Add event platform to ring (#125506)

This commit is contained in:
Steven B. 2024-09-08 19:32:34 +01:00 committed by GitHub
parent 20600123f8
commit 26ac8e35cb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 190 additions and 0 deletions

View file

@ -18,6 +18,7 @@ PLATFORMS = [
Platform.BINARY_SENSOR, Platform.BINARY_SENSOR,
Platform.BUTTON, Platform.BUTTON,
Platform.CAMERA, Platform.CAMERA,
Platform.EVENT,
Platform.LIGHT, Platform.LIGHT,
Platform.SENSOR, Platform.SENSOR,
Platform.SIREN, Platform.SIREN,

View file

@ -0,0 +1,109 @@
"""Component providing support for ring events."""
from dataclasses import dataclass
from typing import Generic
from ring_doorbell import RingCapability, RingEvent as RingAlert
from ring_doorbell.const import KIND_DING, KIND_INTERCOM_UNLOCK, KIND_MOTION
from homeassistant.components.event import (
EventDeviceClass,
EventEntity,
EventEntityDescription,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import RingConfigEntry
from .coordinator import RingListenCoordinator
from .entity import RingBaseEntity, RingDeviceT
@dataclass(frozen=True, kw_only=True)
class RingEventEntityDescription(EventEntityDescription, Generic[RingDeviceT]):
"""Base class for event entity description."""
capability: RingCapability
EVENT_DESCRIPTIONS: tuple[RingEventEntityDescription, ...] = (
RingEventEntityDescription(
key=KIND_DING,
translation_key=KIND_DING,
device_class=EventDeviceClass.DOORBELL,
event_types=[KIND_DING],
capability=RingCapability.DING,
),
RingEventEntityDescription(
key=KIND_MOTION,
translation_key=KIND_MOTION,
device_class=EventDeviceClass.MOTION,
event_types=[KIND_MOTION],
capability=RingCapability.MOTION_DETECTION,
),
RingEventEntityDescription(
key=KIND_INTERCOM_UNLOCK,
translation_key=KIND_INTERCOM_UNLOCK,
device_class=EventDeviceClass.BUTTON,
event_types=[KIND_INTERCOM_UNLOCK],
capability=RingCapability.OPEN,
),
)
async def async_setup_entry(
hass: HomeAssistant,
entry: RingConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up events for a Ring device."""
ring_data = entry.runtime_data
listen_coordinator = ring_data.listen_coordinator
async_add_entities(
RingEvent(device, listen_coordinator, description)
for description in EVENT_DESCRIPTIONS
for device in ring_data.devices.all_devices
if device.has_capability(description.capability)
)
class RingEvent(RingBaseEntity[RingListenCoordinator, RingDeviceT], EventEntity):
"""An event implementation for Ring device."""
entity_description: RingEventEntityDescription[RingDeviceT]
def __init__(
self,
device: RingDeviceT,
coordinator: RingListenCoordinator,
description: RingEventEntityDescription[RingDeviceT],
) -> None:
"""Initialize a event entity for Ring device."""
super().__init__(device, coordinator)
self.entity_description = description
self._attr_unique_id = f"{device.id}-{description.key}"
@callback
def _async_handle_event(self, event: str) -> None:
"""Handle the event."""
self._trigger_event(event)
def _get_coordinator_alert(self) -> RingAlert | None:
return self.coordinator.alerts.get(
(self._device.device_api_id, self.entity_description.key)
)
@callback
def _handle_coordinator_update(self) -> None:
if alert := self._get_coordinator_alert():
self._async_handle_event(alert.kind)
super()._handle_coordinator_update()
@property
def available(self) -> bool:
"""Return if entity is available."""
return self.coordinator.event_listener.started
async def async_update(self) -> None:
"""All updates are passive."""

View file

@ -0,0 +1,80 @@
"""The tests for the Ring event platform."""
from datetime import datetime
import time
from freezegun.api import FrozenDateTimeFactory
import pytest
from ring_doorbell import Ring
from homeassistant.components.ring.binary_sensor import RingEvent
from homeassistant.components.ring.coordinator import RingEventListener
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from .common import setup_platform
from .device_mocks import FRONT_DOOR_DEVICE_ID, INGRESS_DEVICE_ID
@pytest.mark.parametrize(
("device_id", "device_name", "alert_kind", "device_class"),
[
pytest.param(
FRONT_DOOR_DEVICE_ID,
"front_door",
"motion",
"motion",
id="front_door_motion",
),
pytest.param(
FRONT_DOOR_DEVICE_ID, "front_door", "ding", "doorbell", id="front_door_ding"
),
pytest.param(
INGRESS_DEVICE_ID, "ingress", "ding", "doorbell", id="ingress_ding"
),
pytest.param(
INGRESS_DEVICE_ID,
"ingress",
"intercom_unlock",
"button",
id="ingress_unlock",
),
],
)
async def test_event(
hass: HomeAssistant,
mock_ring_client: Ring,
mock_ring_event_listener_class: RingEventListener,
freezer: FrozenDateTimeFactory,
device_id: int,
device_name: str,
alert_kind: str,
device_class: str,
) -> None:
"""Test the Ring event platforms."""
await setup_platform(hass, Platform.EVENT)
start_time_str = "2024-09-04T15:32:53.892+00:00"
start_time = datetime.strptime(start_time_str, "%Y-%m-%dT%H:%M:%S.%f%z")
freezer.move_to(start_time)
on_event_cb = mock_ring_event_listener_class.return_value.add_notification_callback.call_args.args[
0
]
# Default state is unknown
entity_id = f"event.{device_name}_{alert_kind}"
state = hass.states.get(entity_id)
assert state is not None
assert state.state == "unknown"
assert state.attributes["device_class"] == device_class
# A new alert sets to on
event = RingEvent(
1234546, device_id, "Foo", "Bar", time.time(), 180, kind=alert_kind, state=None
)
mock_ring_client.active_alerts.return_value = [event]
on_event_cb(event)
state = hass.states.get(entity_id)
assert state is not None
assert state.state == start_time_str