mirror of
https://github.com/home-assistant/core.git
synced 2026-04-08 16:35:17 +00:00
Compare commits
3 Commits
166107
...
event_ring
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d0bd5329fb | ||
|
|
86229e6da0 | ||
|
|
d070400936 |
@@ -20,7 +20,7 @@ from homeassistant.helpers.typing import ConfigType
|
||||
from homeassistant.util import dt as dt_util
|
||||
from homeassistant.util.hass_dict import HassKey
|
||||
|
||||
from .const import ATTR_EVENT_TYPE, ATTR_EVENT_TYPES, DOMAIN
|
||||
from .const import ATTR_EVENT_TYPE, ATTR_EVENT_TYPES, DOMAIN, DoorbellEventType
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
DATA_COMPONENT: HassKey[EntityComponent[EventEntity]] = HassKey(DOMAIN)
|
||||
@@ -44,6 +44,7 @@ __all__ = [
|
||||
"DOMAIN",
|
||||
"PLATFORM_SCHEMA",
|
||||
"PLATFORM_SCHEMA_BASE",
|
||||
"DoorbellEventType",
|
||||
"EventDeviceClass",
|
||||
"EventEntity",
|
||||
"EventEntityDescription",
|
||||
@@ -189,6 +190,21 @@ class EventEntity(RestoreEntity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_)
|
||||
async def async_internal_added_to_hass(self) -> None:
|
||||
"""Call when the event entity is added to hass."""
|
||||
await super().async_internal_added_to_hass()
|
||||
|
||||
if (
|
||||
self.device_class == EventDeviceClass.DOORBELL
|
||||
and DoorbellEventType.RING not in self.event_types
|
||||
):
|
||||
report_issue = self._suggest_report_issue()
|
||||
_LOGGER.warning(
|
||||
"Entity %s is a doorbell event entity but does not support "
|
||||
"the '%s' event type. This will stop working in "
|
||||
"Home Assistant 2027.4, please %s",
|
||||
self.entity_id,
|
||||
DoorbellEventType.RING,
|
||||
report_issue,
|
||||
)
|
||||
|
||||
if (
|
||||
(state := await self.async_get_last_state())
|
||||
and state.state is not None
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
"""Provides the constants needed for the component."""
|
||||
|
||||
from enum import StrEnum
|
||||
|
||||
DOMAIN = "event"
|
||||
ATTR_EVENT_TYPE = "event_type"
|
||||
ATTR_EVENT_TYPES = "event_types"
|
||||
|
||||
|
||||
class DoorbellEventType(StrEnum):
|
||||
"""Standard event types for doorbell device class."""
|
||||
|
||||
RING = "ring"
|
||||
|
||||
@@ -15,7 +15,14 @@
|
||||
"name": "Button"
|
||||
},
|
||||
"doorbell": {
|
||||
"name": "Doorbell"
|
||||
"name": "Doorbell",
|
||||
"state_attributes": {
|
||||
"event_type": {
|
||||
"state": {
|
||||
"ring": "Ring"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"motion": {
|
||||
"name": "Motion"
|
||||
|
||||
@@ -7,6 +7,7 @@ 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 (
|
||||
DoorbellEventType,
|
||||
EventDeviceClass,
|
||||
EventEntity,
|
||||
EventEntityDescription,
|
||||
@@ -34,7 +35,7 @@ EVENT_DESCRIPTIONS: tuple[RingEventEntityDescription, ...] = (
|
||||
key=KIND_DING,
|
||||
translation_key=KIND_DING,
|
||||
device_class=EventDeviceClass.DOORBELL,
|
||||
event_types=[KIND_DING],
|
||||
event_types=[DoorbellEventType.RING, KIND_DING],
|
||||
capability=RingCapability.DING,
|
||||
),
|
||||
RingEventEntityDescription(
|
||||
@@ -100,6 +101,11 @@ class RingEvent(RingBaseEntity[RingListenCoordinator, RingDeviceT], EventEntity)
|
||||
@callback
|
||||
def _handle_coordinator_update(self) -> None:
|
||||
if (alert := self._get_coordinator_alert()) and not alert.is_update:
|
||||
if alert.kind == KIND_DING:
|
||||
# Fire both the standard "ring" event and the legacy "ding"
|
||||
# event for backward compatibility.
|
||||
self._trigger_event(DoorbellEventType.RING)
|
||||
self.async_write_ha_state()
|
||||
self._async_handle_event(alert.kind)
|
||||
super()._handle_coordinator_update()
|
||||
|
||||
|
||||
@@ -73,7 +73,14 @@
|
||||
},
|
||||
"event": {
|
||||
"ding": {
|
||||
"name": "Ding"
|
||||
"name": "Ding",
|
||||
"state_attributes": {
|
||||
"event_type": {
|
||||
"state": {
|
||||
"ring": "[%key:component::event::entity_component::doorbell::state_attributes::event_type::state::ring%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"intercom_unlock": {
|
||||
"name": "Intercom unlock"
|
||||
|
||||
@@ -10,6 +10,7 @@ from homeassistant.components.event import (
|
||||
ATTR_EVENT_TYPE,
|
||||
ATTR_EVENT_TYPES,
|
||||
DOMAIN,
|
||||
DoorbellEventType,
|
||||
EventDeviceClass,
|
||||
EventEntity,
|
||||
EventEntityDescription,
|
||||
@@ -344,3 +345,76 @@ async def test_name(hass: HomeAssistant) -> None:
|
||||
"device_class": "doorbell",
|
||||
"friendly_name": "Doorbell",
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("config_flow_fixture")
|
||||
async def test_doorbell_missing_ring_event_type(
|
||||
hass: HomeAssistant,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""Test warning when a doorbell entity does not include the standard ring event type."""
|
||||
|
||||
async def async_setup_entry_init(
|
||||
hass: HomeAssistant, config_entry: ConfigEntry
|
||||
) -> bool:
|
||||
"""Set up test config entry."""
|
||||
await hass.config_entries.async_forward_entry_setups(
|
||||
config_entry, [Platform.EVENT]
|
||||
)
|
||||
return True
|
||||
|
||||
mock_platform(hass, f"{TEST_DOMAIN}.config_flow")
|
||||
mock_integration(
|
||||
hass,
|
||||
MockModule(
|
||||
TEST_DOMAIN,
|
||||
async_setup_entry=async_setup_entry_init,
|
||||
),
|
||||
)
|
||||
|
||||
# Doorbell entity WITHOUT the standard "ring" event type
|
||||
entity_without_ring = EventEntity()
|
||||
entity_without_ring._attr_event_types = ["ding"]
|
||||
entity_without_ring._attr_device_class = EventDeviceClass.DOORBELL
|
||||
entity_without_ring._attr_has_entity_name = True
|
||||
entity_without_ring.entity_id = "event.doorbell_without_ring"
|
||||
|
||||
# Doorbell entity WITH the standard "ring" event type
|
||||
entity_with_ring = EventEntity()
|
||||
entity_with_ring._attr_event_types = [DoorbellEventType.RING, "ding"]
|
||||
entity_with_ring._attr_device_class = EventDeviceClass.DOORBELL
|
||||
entity_with_ring._attr_has_entity_name = True
|
||||
entity_with_ring.entity_id = "event.doorbell_with_ring"
|
||||
|
||||
# Non-doorbell entity should not warn
|
||||
entity_button = EventEntity()
|
||||
entity_button._attr_event_types = ["press"]
|
||||
entity_button._attr_device_class = EventDeviceClass.BUTTON
|
||||
entity_button._attr_has_entity_name = True
|
||||
entity_button.entity_id = "event.button"
|
||||
|
||||
async def async_setup_entry_platform(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up test event platform via config entry."""
|
||||
async_add_entities([entity_without_ring, entity_with_ring, entity_button])
|
||||
|
||||
mock_platform(
|
||||
hass,
|
||||
f"{TEST_DOMAIN}.{DOMAIN}",
|
||||
MockPlatform(async_setup_entry=async_setup_entry_platform),
|
||||
)
|
||||
|
||||
config_entry = MockConfigEntry(domain=TEST_DOMAIN)
|
||||
config_entry.add_to_hass(hass)
|
||||
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert (
|
||||
"Entity event.doorbell_without_ring is a doorbell event entity "
|
||||
"but does not support the 'ring' event type"
|
||||
) in caplog.text
|
||||
assert "event.doorbell_with_ring" not in caplog.text
|
||||
assert "event.button" not in caplog.text
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'event_types': list([
|
||||
<DoorbellEventType.RING: 'ring'>,
|
||||
'ding',
|
||||
]),
|
||||
}),
|
||||
@@ -47,6 +48,7 @@
|
||||
'device_class': 'doorbell',
|
||||
'event_type': None,
|
||||
'event_types': list([
|
||||
<DoorbellEventType.RING: 'ring'>,
|
||||
'ding',
|
||||
]),
|
||||
'friendly_name': 'Front Door Ding',
|
||||
@@ -187,6 +189,7 @@
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'event_types': list([
|
||||
<DoorbellEventType.RING: 'ring'>,
|
||||
'ding',
|
||||
]),
|
||||
}),
|
||||
@@ -227,6 +230,7 @@
|
||||
'device_class': 'doorbell',
|
||||
'event_type': None,
|
||||
'event_types': list([
|
||||
<DoorbellEventType.RING: 'ring'>,
|
||||
'ding',
|
||||
]),
|
||||
'friendly_name': 'Ingress Ding',
|
||||
|
||||
@@ -9,10 +9,11 @@ import pytest
|
||||
from ring_doorbell import Ring
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
|
||||
from homeassistant.components.event import DoorbellEventType
|
||||
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 homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
from .common import MockConfigEntry, setup_platform
|
||||
@@ -96,3 +97,49 @@ async def test_event(
|
||||
state = hass.states.get(entity_id)
|
||||
assert state is not None
|
||||
assert state.state == start_time_str
|
||||
|
||||
|
||||
async def test_doorbell_ding_fires_standard_ring_event(
|
||||
hass: HomeAssistant,
|
||||
mock_ring_client: Ring,
|
||||
mock_ring_event_listener_class: RingEventListener,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
) -> None:
|
||||
"""Test that a doorbell ding fires both 'ring' and 'ding' events."""
|
||||
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
|
||||
]
|
||||
|
||||
entity_id = "event.front_door_ding"
|
||||
event_types: list[str] = []
|
||||
|
||||
@callback
|
||||
def state_listener(event: object) -> None:
|
||||
state = hass.states.get(entity_id)
|
||||
assert state
|
||||
event_types.append(state.attributes["event_type"])
|
||||
|
||||
hass.bus.async_listen("state_changed", state_listener)
|
||||
|
||||
event = RingEvent(
|
||||
1234546,
|
||||
FRONT_DOOR_DEVICE_ID,
|
||||
"Foo",
|
||||
"Bar",
|
||||
time.time(),
|
||||
180,
|
||||
kind="ding",
|
||||
state=None,
|
||||
)
|
||||
mock_ring_client.active_alerts.return_value = [event]
|
||||
on_event_cb(event)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Both the standard "ring" and legacy "ding" events should have fired
|
||||
assert DoorbellEventType.RING in event_types
|
||||
assert "ding" in event_types
|
||||
|
||||
Reference in New Issue
Block a user