Home Assistant Git Exporter

This commit is contained in:
root
2024-08-26 13:38:09 +02:00
parent 80fc630f5e
commit fc0376e38e
2010 changed files with 11414 additions and 16153 deletions
@@ -38,6 +38,7 @@ from .const import (
ATTR_CLIENT,
ATTR_CONFIG,
ATTR_COORDINATOR,
ATTR_WS_EVENT_PROXY,
ATTRIBUTE_LABELS,
CONF_CAMERA_STATIC_IMAGE_HEIGHT,
DOMAIN,
@@ -52,6 +53,7 @@ from .const import (
)
from .views import async_setup as views_async_setup
from .ws_api import async_setup as ws_api_async_setup
from .ws_event_proxy import WSEventProxy
SCAN_INTERVAL = timedelta(seconds=5)
@@ -215,11 +217,15 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
model = f"{(await async_get_integration(hass, DOMAIN)).version}/{server_version}"
ws_event_proxy = WSEventProxy(config["mqtt"]["topic_prefix"])
entry.async_on_unload(lambda: ws_event_proxy.unsubscribe_all(hass))
hass.data[DOMAIN][entry.entry_id] = {
ATTR_COORDINATOR: coordinator,
ATTR_CLIENT: client,
ATTR_CONFIG: config,
ATTR_MODEL: model,
ATTR_WS_EVENT_PROXY: ws_event_proxy,
}
# Remove old devices associated with cameras that have since been removed
@@ -38,6 +38,7 @@ ATTR_PLAYBACK_FACTOR = "playback_factor"
ATTR_PTZ_ACTION = "action"
ATTR_PTZ_ARGUMENT = "argument"
ATTR_START_TIME = "start_time"
ATTR_WS_EVENT_PROXY = "ws_event_proxy"
# Frigate Attribute Labels
# These are labels that are not individually tracked as they are
@@ -14,5 +14,5 @@
"iot_class": "local_push",
"issue_tracker": "https://github.com/blakeblackshear/frigate-hass-integration/issues",
"requirements": ["pytz"],
"version": "5.3.0"
"version": "5.4.0"
}
+72 -2
View File
@@ -6,7 +6,12 @@ import logging
import voluptuous as vol
from custom_components.frigate.api import FrigateApiClient, FrigateApiClientError
from custom_components.frigate.views import get_client_for_frigate_instance_id
from custom_components.frigate.const import ATTR_WS_EVENT_PROXY, DOMAIN
from custom_components.frigate.views import (
get_client_for_frigate_instance_id,
get_config_entry_for_frigate_instance_id,
)
from custom_components.frigate.ws_event_proxy import WSEventProxy
from homeassistant.components import websocket_api
from homeassistant.core import HomeAssistant
@@ -21,6 +26,8 @@ def async_setup(hass: HomeAssistant) -> None:
websocket_api.async_register_command(hass, ws_get_events)
websocket_api.async_register_command(hass, ws_get_events_summary)
websocket_api.async_register_command(hass, ws_get_ptz_info)
websocket_api.async_register_command(hass, ws_subscribe_events)
websocket_api.async_register_command(hass, ws_unsubscribe_events)
def _get_client_or_send_error(
@@ -157,7 +164,6 @@ async def ws_get_recordings_summary(
vol.Optional("limit"): int,
vol.Optional("has_clip"): bool,
vol.Optional("has_snapshot"): bool,
vol.Optional("has_snapshot"): bool,
vol.Optional("favorites"): bool,
}
) # type: ignore[misc]
@@ -237,6 +243,70 @@ async def ws_get_events_summary(
)
@websocket_api.websocket_command(
{
vol.Required("type"): "frigate/events/subscribe",
vol.Required("instance_id"): str,
}
) # type: ignore[misc]
@websocket_api.async_response # type: ignore[misc]
async def ws_subscribe_events(
hass: HomeAssistant,
connection: websocket_api.ActiveConnection,
msg: dict,
) -> None:
"""Subscribe to events."""
entry = get_config_entry_for_frigate_instance_id(hass, msg["instance_id"])
if not entry:
connection.send_error(
msg["id"],
"not_found",
f"API error whilst subscribing to events for unknown Frigate instance "
f"{msg['instance_id']}",
)
return
event_proxy: WSEventProxy = hass.data[DOMAIN][entry.entry_id][ATTR_WS_EVENT_PROXY]
connection.send_result(
msg["id"], await event_proxy.subscribe(hass, msg["id"], connection)
)
@websocket_api.websocket_command(
{
vol.Required("type"): "frigate/events/unsubscribe",
vol.Required("instance_id"): str,
vol.Required("subscription_id"): int,
}
) # type: ignore[misc]
@websocket_api.async_response # type: ignore[misc]
async def ws_unsubscribe_events(
hass: HomeAssistant,
connection: websocket_api.ActiveConnection,
msg: dict,
) -> None:
"""Unsubscribe from events."""
entry = get_config_entry_for_frigate_instance_id(hass, msg["instance_id"])
if not entry:
connection.send_error(
msg["id"],
"not_found",
f"API error whilst unsubscribing to events for unknown Frigate instance "
f"{msg['instance_id']}",
)
return
event_proxy: WSEventProxy = hass.data[DOMAIN][entry.entry_id][ATTR_WS_EVENT_PROXY]
if event_proxy.unsubscribe(hass, msg["subscription_id"]):
connection.send_result(msg["id"])
else:
connection.send_error(
msg["id"], websocket_api.const.ERR_NOT_FOUND, "Subscription not found."
)
@websocket_api.websocket_command(
{
vol.Required("type"): "frigate/ptz/info",
@@ -0,0 +1,95 @@
"""Frigate event proxy."""
from __future__ import annotations
import logging
from homeassistant.components import websocket_api
from homeassistant.components.mqtt.models import ReceiveMessage
from homeassistant.components.mqtt.subscription import (
async_prepare_subscribe_topics,
async_subscribe_topics,
async_unsubscribe_topics,
)
from homeassistant.components.websocket_api import messages
from homeassistant.core import HomeAssistant
_LOGGER: logging.Logger = logging.getLogger(__name__)
class WSEventProxy:
"""Frigate event MQTT to WS proxy.
This class subscribes to the MQTT events topic for a given Frigate topic and
forwards the messages to a list of subscribers. MQTT payload is directly
passed to subscribers to avoid JSON serialization/deserialization overhead
within HA.
"""
def __init__(self, topic_prefix: str) -> None:
self._subscriptions: dict[int, websocket_api.ActiveConnection] = {}
self._topics = {
"events": {
"topic": f"{topic_prefix}/events",
"msg_callback": lambda msg: self._receive_message(msg),
"qos": 0,
}
}
self._sub_state = None
async def subscribe(
self,
hass: HomeAssistant,
subscription_id: int,
connection: websocket_api.ActiveConnection,
) -> int:
"""Subscribe to events."""
if self._sub_state is None:
self._sub_state = async_prepare_subscribe_topics(
hass, self._sub_state, self._topics
)
await async_subscribe_topics(hass, self._sub_state)
# Add a callback to the websocket to unsubscribe if closed.
connection.subscriptions[subscription_id] = lambda: self._unsubscribe_internal(
hass, subscription_id
)
self._subscriptions[subscription_id] = connection
return subscription_id
def unsubscribe(self, hass: HomeAssistant, subscription_id: int) -> bool:
"""Unsubscribe from events."""
if (
subscription_id in self._subscriptions
and subscription_id in self._subscriptions[subscription_id].subscriptions
):
self._subscriptions[subscription_id].subscriptions.pop(subscription_id)
return self._unsubscribe_internal(hass, subscription_id)
def _unsubscribe_internal(self, hass: HomeAssistant, subscription_id: int) -> bool:
"""Unsubscribe from events.
May be called from the websocket connection close handler. As a result
must not change the size of connection.subscriptions which is iterated
over in that handler.
"""
if subscription_id not in self._subscriptions:
return False
self._subscriptions.pop(subscription_id)
if not self._subscriptions:
async_unsubscribe_topics(hass, self._sub_state)
self._sub_state = None
return True
def unsubscribe_all(self, hass: HomeAssistant) -> None:
"""Unsubscribe all subscribers."""
for subscription_id in list(self._subscriptions.keys()):
self.unsubscribe(hass, subscription_id)
def _receive_message(self, msg: ReceiveMessage) -> None:
"""Handle a new received MQTT message."""
for id, connection in self._subscriptions.items():
connection.send_message(messages.event_message(id, msg.payload))