From c22cf3b3d2e4d079499473500abf8a73e5b80163 Mon Sep 17 00:00:00 2001 From: Graham Arthur Blair Date: Thu, 5 May 2022 16:12:51 -0700 Subject: [PATCH] Add buttons to Ring chime devices to play ding and motion chimes (#71370) Co-authored-by: Paulus Schoutsen --- homeassistant/components/ring/__init__.py | 1 + homeassistant/components/ring/button.py | 65 +++++++++++++++++++++++ tests/components/ring/test_button.py | 55 +++++++++++++++++++ 3 files changed, 121 insertions(+) create mode 100644 homeassistant/components/ring/button.py create mode 100644 tests/components/ring/test_button.py diff --git a/homeassistant/components/ring/__init__.py b/homeassistant/components/ring/__init__.py index 5d834976370..2a922a7bb3e 100644 --- a/homeassistant/components/ring/__init__.py +++ b/homeassistant/components/ring/__init__.py @@ -32,6 +32,7 @@ DEFAULT_ENTITY_NAMESPACE = "ring" PLATFORMS = [ Platform.BINARY_SENSOR, + Platform.BUTTON, Platform.LIGHT, Platform.SENSOR, Platform.SWITCH, diff --git a/homeassistant/components/ring/button.py b/homeassistant/components/ring/button.py new file mode 100644 index 00000000000..04f9f7950f8 --- /dev/null +++ b/homeassistant/components/ring/button.py @@ -0,0 +1,65 @@ +"""This component provides HA button support for Ring Chimes.""" +import logging + +from homeassistant.components.button import ButtonEntity +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from . import DOMAIN +from .entity import RingEntityMixin + +_LOGGER = logging.getLogger(__name__) + +BELL_ICON = "mdi:bell-ring" + + +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]["devices"] + buttons = [] + + # add one button for each test chime type (ding, motion) + for device in devices["chimes"]: + buttons.append(ChimeButton(config_entry.entry_id, device, "ding")) + buttons.append(ChimeButton(config_entry.entry_id, device, "motion")) + + async_add_entities(buttons) + + +class BaseRingButton(RingEntityMixin, ButtonEntity): + """Represents a Button for controlling an aspect of a ring device.""" + + def __init__(self, config_entry_id, device, button_identifier, button_name): + """Initialize the switch.""" + super().__init__(config_entry_id, device) + self._button_identifier = button_identifier + self._button_name = button_name + self._attr_unique_id = f"{self._device.id}-{self._button_identifier}" + + @property + def name(self): + """Name of the device.""" + return f"{self._device.name} {self._button_name}" + + +class ChimeButton(BaseRingButton): + """Creates a button to play the test chime of a Chime device.""" + + _attr_icon = BELL_ICON + + def __init__(self, config_entry_id, device, kind): + """Initialize the button for a device with a chime.""" + super().__init__( + config_entry_id, device, f"play-chime-{kind}", f"Play chime: {kind}" + ) + self.kind = kind + + def press(self) -> None: + """Send the test chime request.""" + if not self._device.test_sound(kind=self.kind): + _LOGGER.error("Failed to ring chime sound on %s", self.name) diff --git a/tests/components/ring/test_button.py b/tests/components/ring/test_button.py new file mode 100644 index 00000000000..62b2fcd8a78 --- /dev/null +++ b/tests/components/ring/test_button.py @@ -0,0 +1,55 @@ +"""The tests for the Ring button platform.""" + +from homeassistant.const import Platform +from homeassistant.helpers import entity_registry as er + +from .common import setup_platform + + +async def test_entity_registry(hass, requests_mock): + """Tests that the devices are registered in the entity registry.""" + await setup_platform(hass, Platform.BUTTON) + entity_registry = er.async_get(hass) + + entry = entity_registry.async_get("button.downstairs_play_chime_ding") + assert entry.unique_id == "123456-play-chime-ding" + + entry = entity_registry.async_get("button.downstairs_play_chime_motion") + assert entry.unique_id == "123456-play-chime-motion" + + +async def test_play_chime_buttons_report_correctly(hass, requests_mock): + """Tests that the initial state of a device that should be on is correct.""" + await setup_platform(hass, Platform.BUTTON) + + state = hass.states.get("button.downstairs_play_chime_ding") + assert state.attributes.get("friendly_name") == "Downstairs Play chime: ding" + assert state.attributes.get("icon") == "mdi:bell-ring" + + state = hass.states.get("button.downstairs_play_chime_motion") + assert state.attributes.get("friendly_name") == "Downstairs Play chime: motion" + assert state.attributes.get("icon") == "mdi:bell-ring" + + +async def test_chime_can_be_played(hass, requests_mock): + """Tests the play chime request is sent correctly.""" + await setup_platform(hass, Platform.BUTTON) + + # Mocks the response for playing a test sound + requests_mock.post( + "https://api.ring.com/clients_api/chimes/123456/play_sound", + text="SUCCESS", + ) + await hass.services.async_call( + "button", + "press", + {"entity_id": "button.downstairs_play_chime_ding"}, + blocking=True, + ) + + await hass.async_block_till_done() + + assert requests_mock.request_history[-1].url.startswith( + "https://api.ring.com/clients_api/chimes/123456/play_sound?" + ) + assert "kind=ding" in requests_mock.request_history[-1].url