Add event platform to ring (#125506)
This commit is contained in:
parent
20600123f8
commit
26ac8e35cb
3 changed files with 190 additions and 0 deletions
|
@ -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,
|
||||||
|
|
109
homeassistant/components/ring/event.py
Normal file
109
homeassistant/components/ring/event.py
Normal 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."""
|
80
tests/components/ring/test_event.py
Normal file
80
tests/components/ring/test_event.py
Normal 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
|
Loading…
Add table
Reference in a new issue