From 57568fdc2c4f2ba581b5db16145ea5d293bd1cb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ab=C3=ADlio=20Costa?= Date: Thu, 9 Apr 2026 00:02:05 +0100 Subject: [PATCH] Add standard event type for doorbell event entities (#167630) --- homeassistant/components/event/__init__.py | 18 +++++- homeassistant/components/event/const.py | 8 +++ homeassistant/components/event/strings.json | 9 ++- tests/components/event/test_init.py | 67 +++++++++++++++++++++ 4 files changed, 100 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/event/__init__.py b/homeassistant/components/event/__init__.py index 4ed5a0f1378b..4a4914abf8bb 100644 --- a/homeassistant/components/event/__init__.py +++ b/homeassistant/components/event/__init__.py @@ -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 diff --git a/homeassistant/components/event/const.py b/homeassistant/components/event/const.py index cd6a8b96f7a3..5bab58750526 100644 --- a/homeassistant/components/event/const.py +++ b/homeassistant/components/event/const.py @@ -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" diff --git a/homeassistant/components/event/strings.json b/homeassistant/components/event/strings.json index bdf9144761cd..1b5e349b8f35 100644 --- a/homeassistant/components/event/strings.json +++ b/homeassistant/components/event/strings.json @@ -15,7 +15,14 @@ "name": "Button" }, "doorbell": { - "name": "Doorbell" + "name": "Doorbell", + "state_attributes": { + "event_type": { + "state": { + "ring": "Ring" + } + } + } }, "motion": { "name": "Motion" diff --git a/tests/components/event/test_init.py b/tests/components/event/test_init.py index 0cd1f39228fc..0df0b152d426 100644 --- a/tests/components/event/test_init.py +++ b/tests/components/event/test_init.py @@ -10,6 +10,7 @@ from homeassistant.components.event import ( ATTR_EVENT_TYPE, ATTR_EVENT_TYPES, DOMAIN, + DoorbellEventType, EventDeviceClass, EventEntity, EventEntityDescription, @@ -34,6 +35,7 @@ from tests.common import ( mock_platform, mock_restore_cache, mock_restore_cache_with_extra_data, + setup_test_component_platform, ) @@ -344,3 +346,68 @@ 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" + + setup_test_component_platform( + hass, + DOMAIN, + [entity_without_ring, entity_with_ring, entity_button], + from_config_entry=True, + ) + 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