From 4107cd6ad8e543e2e07c18487a486f515de08e75 Mon Sep 17 00:00:00 2001 From: cosimomeli Date: Fri, 15 Mar 2024 15:31:51 +0100 Subject: [PATCH] Add Ring Intercom open door button (#113514) * Add button * Make Ruff happy * Apply suggestions from code review Co-authored-by: Joost Lekkerkerker * Fix doc string * Format * Update tests/components/ring/test_button.py --------- Co-authored-by: Joost Lekkerkerker --- homeassistant/components/ring/button.py | 57 ++++++++++++++++++++++ homeassistant/components/ring/const.py | 1 + homeassistant/components/ring/icons.json | 3 ++ homeassistant/components/ring/strings.json | 5 ++ tests/components/ring/conftest.py | 11 +++++ tests/components/ring/test_button.py | 42 ++++++++++++++++ 6 files changed, 119 insertions(+) create mode 100644 homeassistant/components/ring/button.py create mode 100644 tests/components/ring/test_button.py diff --git a/homeassistant/components/ring/button.py b/homeassistant/components/ring/button.py new file mode 100644 index 00000000000..343c0d68257 --- /dev/null +++ b/homeassistant/components/ring/button.py @@ -0,0 +1,57 @@ +"""Component providing support for Ring buttons.""" + +from __future__ import annotations + +from homeassistant.components.button import ButtonEntity, ButtonEntityDescription +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from .const import DOMAIN, RING_DEVICES, RING_DEVICES_COORDINATOR +from .coordinator import RingDataCoordinator +from .entity import RingEntity, exception_wrap + +BUTTON_DESCRIPTION = ButtonEntityDescription( + key="open_door", translation_key="open_door" +) + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Create the buttons for the Ring devices.""" + devices = hass.data[DOMAIN][config_entry.entry_id][RING_DEVICES] + devices_coordinator: RingDataCoordinator = hass.data[DOMAIN][config_entry.entry_id][ + RING_DEVICES_COORDINATOR + ] + + async_add_entities( + RingDoorButton(device, devices_coordinator, BUTTON_DESCRIPTION) + for device in devices["other"] + if device.has_capability("open") + ) + + +class RingDoorButton(RingEntity, ButtonEntity): + """Creates a button to open the ring intercom door.""" + + def __init__( + self, + device, + coordinator, + description: ButtonEntityDescription, + ) -> None: + """Initialize the button.""" + super().__init__( + device, + coordinator, + ) + self.entity_description = description + self._attr_unique_id = f"{device.id}-{description.key}" + + @exception_wrap + def press(self) -> None: + """Open the door.""" + self._device.open_door() diff --git a/homeassistant/components/ring/const.py b/homeassistant/components/ring/const.py index 24c584016d5..23f378a38be 100644 --- a/homeassistant/components/ring/const.py +++ b/homeassistant/components/ring/const.py @@ -16,6 +16,7 @@ DEFAULT_ENTITY_NAMESPACE = "ring" PLATFORMS = [ Platform.BINARY_SENSOR, + Platform.BUTTON, Platform.CAMERA, Platform.LIGHT, Platform.SENSOR, diff --git a/homeassistant/components/ring/icons.json b/homeassistant/components/ring/icons.json index 9ce0de6bebc..9dd31fd0fd1 100644 --- a/homeassistant/components/ring/icons.json +++ b/homeassistant/components/ring/icons.json @@ -22,6 +22,9 @@ "voice_volume": { "default": "mdi:account-voice" }, + "open_door": { + "default": "mdi:door-closed-lock" + }, "wifi_signal_category": { "default": "mdi:wifi" }, diff --git a/homeassistant/components/ring/strings.json b/homeassistant/components/ring/strings.json index de8b5112ec9..142c533fcfc 100644 --- a/homeassistant/components/ring/strings.json +++ b/homeassistant/components/ring/strings.json @@ -37,6 +37,11 @@ "name": "Ding" } }, + "button": { + "open_door": { + "name": "Open door" + } + }, "light": { "light": { "name": "[%key:component::light::title%]" diff --git a/tests/components/ring/conftest.py b/tests/components/ring/conftest.py index 106a824f1d5..70c067af887 100644 --- a/tests/components/ring/conftest.py +++ b/tests/components/ring/conftest.py @@ -131,4 +131,15 @@ def requests_mock_fixture(): ), text="ok", ) + # Mocks the open door command for intercom devices + mock.put( + "https://api.ring.com/commands/v1/devices/185036587/device_rpc", + status_code=200, + text="{}", + ) + # Mocks the response for getting the history of the intercom + mock.get( + "https://api.ring.com/clients_api/doorbots/185036587/history", + text=load_fixture("intercom_history.json", "ring"), + ) yield mock diff --git a/tests/components/ring/test_button.py b/tests/components/ring/test_button.py new file mode 100644 index 00000000000..6f0c29b1fcc --- /dev/null +++ b/tests/components/ring/test_button.py @@ -0,0 +1,42 @@ +"""The tests for the Ring button platform.""" + +import requests_mock + +from homeassistant.const import Platform +from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er + +from .common import setup_platform + + +async def test_entity_registry( + hass: HomeAssistant, + requests_mock: requests_mock.Mocker, + entity_registry: er.EntityRegistry, +) -> None: + """Tests that the devices are registered in the entity registry.""" + await setup_platform(hass, Platform.BUTTON) + + entry = entity_registry.async_get("button.ingress_open_door") + assert entry.unique_id == "185036587-open_door" + + +async def test_button_opens_door( + hass: HomeAssistant, requests_mock: requests_mock.Mocker +) -> None: + """Tests the door open button works correctly.""" + await setup_platform(hass, Platform.BUTTON) + + # Mocks the response for opening door + mock = requests_mock.put( + "https://api.ring.com/commands/v1/devices/185036587/device_rpc", + status_code=200, + text="{}", + ) + + await hass.services.async_call( + "button", "press", {"entity_id": "button.ingress_open_door"}, blocking=True + ) + + await hass.async_block_till_done() + assert mock.called_once