diff --git a/custom_components/frigate/camera.py b/custom_components/frigate/camera.py index e22cf2ae..1d18b952 100644 --- a/custom_components/frigate/camera.py +++ b/custom_components/frigate/camera.py @@ -7,13 +7,16 @@ import aiohttp import async_timeout from jinja2 import Template +import voluptuous as vol from yarl import URL +from custom_components.frigate.api import FrigateApiClient from homeassistant.components.camera import Camera, CameraEntityFeature from homeassistant.components.mqtt import async_publish from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_URL from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers import entity_platform from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -28,10 +31,14 @@ get_frigate_entity_unique_id, ) from .const import ( + ATTR_CLIENT, ATTR_CONFIG, + ATTR_EVENT_ID, + ATTR_FAVORITE, CONF_RTMP_URL_TEMPLATE, DOMAIN, NAME, + SERVICE_FAVORITE_EVENT, STATE_DETECTED, STATE_IDLE, ) @@ -45,10 +52,13 @@ async def async_setup_entry( """Camera entry setup.""" frigate_config = hass.data[DOMAIN][entry.entry_id][ATTR_CONFIG] + frigate_client = hass.data[DOMAIN][entry.entry_id][ATTR_CLIENT] async_add_entities( [ - FrigateCamera(entry, cam_name, frigate_config, camera_config) + FrigateCamera( + entry, cam_name, frigate_client, frigate_config, camera_config + ) for cam_name, camera_config in frigate_config["cameras"].items() ] + [ @@ -57,6 +67,14 @@ async def async_setup_entry( ] ) + # setup services + platform = entity_platform.async_get_current_platform() + platform.async_register_entity_service( + SERVICE_FAVORITE_EVENT, + {vol.Required(ATTR_EVENT_ID): str, vol.Required(ATTR_FAVORITE): bool}, + SERVICE_FAVORITE_EVENT, + ) + class FrigateCamera(FrigateMQTTEntity, Camera): # type: ignore[misc] """Representation a Frigate camera.""" @@ -65,10 +83,12 @@ def __init__( self, config_entry: ConfigEntry, cam_name: str, + frigate_client: FrigateApiClient, frigate_config: dict[str, Any], camera_config: dict[str, Any], ) -> None: """Initialize a Frigate camera.""" + self._client = frigate_client self._frigate_config = frigate_config self._camera_config = camera_config self._cam_name = cam_name @@ -214,6 +234,10 @@ async def async_disable_motion_detection(self) -> None: False, ) + async def favorite_event(self, event_id: str, favorite: bool) -> None: + """Favorite an event.""" + await self._client.async_retain(event_id, favorite) + class FrigateMqttSnapshots(FrigateMQTTEntity, Camera): # type: ignore[misc] """Frigate best camera class.""" diff --git a/custom_components/frigate/const.py b/custom_components/frigate/const.py index 3b2bf8c9..923dafd8 100644 --- a/custom_components/frigate/const.py +++ b/custom_components/frigate/const.py @@ -33,10 +33,12 @@ # Attributes ATTR_CLIENT = "client" +ATTR_CLIENT_ID = "client_id" ATTR_CONFIG = "config" ATTR_COORDINATOR = "coordinator" +ATTR_EVENT_ID = "event_id" +ATTR_FAVORITE = "favorite" ATTR_MQTT = "mqtt" -ATTR_CLIENT_ID = "client_id" # Configuration and options CONF_CAMERA_STATIC_IMAGE_HEIGHT = "camera_image_height" @@ -69,3 +71,6 @@ STATUS_ERROR = "error" STATUS_RUNNING = "running" STATUS_STARTING = "starting" + +# Frigate Services +SERVICE_FAVORITE_EVENT = "favorite_event" diff --git a/custom_components/frigate/services.yaml b/custom_components/frigate/services.yaml new file mode 100644 index 00000000..882f8dec --- /dev/null +++ b/custom_components/frigate/services.yaml @@ -0,0 +1,20 @@ +--- +favorite_event: + name: Favorite Event + description: Favorites or unfavorites an event indefinitely. + target: + fields: + event_id: + name: Event ID + description: ID of the event to retain. + required: true + advanced: false + example: "1656510950.19548-ihtjj7" + default: "" + favorite: + name: Favorite + description: If the event should be favorited / unfavorited. + required: true + advanced: false + example: true + default: true diff --git a/tests/test_camera.py b/tests/test_camera.py index c1516c6b..098b882f 100644 --- a/tests/test_camera.py +++ b/tests/test_camera.py @@ -9,7 +9,14 @@ import pytest from pytest_homeassistant_custom_component.common import async_fire_mqtt_message -from custom_components.frigate.const import CONF_RTMP_URL_TEMPLATE, DOMAIN, NAME +from custom_components.frigate.const import ( + ATTR_EVENT_ID, + ATTR_FAVORITE, + CONF_RTMP_URL_TEMPLATE, + DOMAIN, + NAME, + SERVICE_FAVORITE_EVENT, +) from homeassistant.components.camera import ( DOMAIN as CAMERA_DOMAIN, SERVICE_DISABLE_MOTION, @@ -302,3 +309,39 @@ async def test_cameras_setup_correctly_in_registry( TEST_CAMERA_FRONT_DOOR_PERSON_ENTITY_ID, }, ) + + +async def test_retain_service_call( + hass: HomeAssistant, +) -> None: + """Test retain service call.""" + post_success = {"success": True, "message": "Post success"} + + client = create_mock_frigate_client() + client.async_retain = AsyncMock(return_value=post_success) + await setup_mock_frigate_config_entry(hass, client=client) + + event_id = "1656282822.206673-bovnfg" + await hass.services.async_call( + DOMAIN, + SERVICE_FAVORITE_EVENT, + { + ATTR_ENTITY_ID: TEST_CAMERA_FRONT_DOOR_ENTITY_ID, + ATTR_EVENT_ID: event_id, + ATTR_FAVORITE: True, + }, + blocking=True, + ) + client.async_retain.assert_called_with(event_id, True) + + await hass.services.async_call( + DOMAIN, + SERVICE_FAVORITE_EVENT, + { + ATTR_ENTITY_ID: TEST_CAMERA_FRONT_DOOR_ENTITY_ID, + ATTR_EVENT_ID: event_id, + ATTR_FAVORITE: False, + }, + blocking=True, + ) + client.async_retain.assert_called_with(event_id, False)