diff --git a/homeassistant/components/ring/icons.json b/homeassistant/components/ring/icons.json index 0798d910b7b..a5411e3e54f 100644 --- a/homeassistant/components/ring/icons.json +++ b/homeassistant/components/ring/icons.json @@ -49,6 +49,12 @@ "switch": { "siren": { "default": "mdi:alarm-bell" + }, + "in_home_chime": { + "default": "mdi:bell-ring-outline", + "state": { + "on": "mdi:bell-ring" + } } } } diff --git a/homeassistant/components/ring/strings.json b/homeassistant/components/ring/strings.json index 201832b9465..1094b3abd42 100644 --- a/homeassistant/components/ring/strings.json +++ b/homeassistant/components/ring/strings.json @@ -109,6 +109,9 @@ "switch": { "siren": { "name": "[%key:component::siren::title%]" + }, + "in_home_chime": { + "name": "In-home chime" } } }, diff --git a/homeassistant/components/ring/switch.py b/homeassistant/components/ring/switch.py index f3a7d9a1252..79c049792db 100644 --- a/homeassistant/components/ring/switch.py +++ b/homeassistant/components/ring/switch.py @@ -5,7 +5,8 @@ from dataclasses import dataclass import logging from typing import Any, Generic, Self, cast -from ring_doorbell import RingCapability, RingStickUpCam +from ring_doorbell import RingCapability, RingDoorBell, RingStickUpCam +from ring_doorbell.const import DOORBELL_EXISTING_TYPE from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription from homeassistant.const import Platform @@ -26,6 +27,8 @@ from .entity import ( _LOGGER = logging.getLogger(__name__) +IN_HOME_CHIME_IS_PRESENT = {v for k, v in DOORBELL_EXISTING_TYPE.items() if k != 2} + @dataclass(frozen=True, kw_only=True) class RingSwitchEntityDescription( @@ -54,6 +57,17 @@ SWITCHES: Sequence[RingSwitchEntityDescription[Any]] = ( new_platform=Platform.SIREN, breaks_in_ha_version="2025.4.0" ), ), + RingSwitchEntityDescription[RingDoorBell]( + key="in_home_chime", + translation_key="in_home_chime", + exists_fn=lambda device: device.family == "doorbots" + and device.existing_doorbell_type in IN_HOME_CHIME_IS_PRESENT, + is_on_fn=lambda device: device.existing_doorbell_type_enabled or False, + turn_on_fn=lambda device: device.async_set_existing_doorbell_type_enabled(True), + turn_off_fn=lambda device: device.async_set_existing_doorbell_type_enabled( + False + ), + ), ) diff --git a/tests/components/ring/device_mocks.py b/tests/components/ring/device_mocks.py index cdb93d9911d..99ee6cd11be 100644 --- a/tests/components/ring/device_mocks.py +++ b/tests/components/ring/device_mocks.py @@ -18,6 +18,7 @@ from ring_doorbell import ( RingOther, RingStickUpCam, ) +from ring_doorbell.const import DOORBELL_EXISTING_TYPE from homeassistant.components.ring.const import DOMAIN from homeassistant.util import dt as dt_util @@ -173,6 +174,21 @@ def _mocked_ring_device(device_dict, device_family, device_class, capabilities): ) ) + if device_family == "doorbots": + mock_device.configure_mock( + existing_doorbell_type=DOORBELL_EXISTING_TYPE[ + device_dict["settings"]["chime_settings"].get("type", 2) + ] + ) + mock_device.configure_mock( + existing_doorbell_type_enabled=device_dict["settings"][ + "chime_settings" + ].get("enable", False) + ) + mock_device.async_set_existing_doorbell_type_enabled.side_effect = ( + lambda i: mock_device.configure_mock(existing_doorbell_type_enabled=i) + ) + if device_family == "other": for prop in ("doorbell_volume", "mic_volume", "voice_volume"): mock_device.configure_mock( diff --git a/tests/components/ring/snapshots/test_switch.ambr b/tests/components/ring/snapshots/test_switch.ambr index 2d56cf3ad13..c45b36c430b 100644 --- a/tests/components/ring/snapshots/test_switch.ambr +++ b/tests/components/ring/snapshots/test_switch.ambr @@ -1,4 +1,51 @@ # serializer version: 1 +# name: test_states[switch.front_door_in_home_chime-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'switch', + 'entity_category': None, + 'entity_id': 'switch.front_door_in_home_chime', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'In-home chime', + 'platform': 'ring', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'in_home_chime', + 'unique_id': '987654-in_home_chime', + 'unit_of_measurement': None, + }) +# --- +# name: test_states[switch.front_door_in_home_chime-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Ring.com', + 'friendly_name': 'Front Door In-home chime', + }), + 'context': , + 'entity_id': 'switch.front_door_in_home_chime', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'on', + }) +# --- # name: test_states[switch.front_siren-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ diff --git a/tests/components/ring/test_switch.py b/tests/components/ring/test_switch.py index c0d49ad2896..a29dbf72cde 100644 --- a/tests/components/ring/test_switch.py +++ b/tests/components/ring/test_switch.py @@ -103,35 +103,44 @@ async def test_siren_on_reports_correctly( assert state.attributes.get("friendly_name") == "Internal Siren" -async def test_siren_can_be_turned_on_and_off( - hass: HomeAssistant, mock_ring_client, create_deprecated_siren_entity +@pytest.mark.parametrize( + ("entity_id"), + [ + ("switch.front_siren"), + ("switch.front_door_in_home_chime"), + ], +) +async def test_switch_can_be_turned_on_and_off( + hass: HomeAssistant, + mock_ring_client, + create_deprecated_siren_entity, + entity_id, ) -> None: - """Tests the siren turns on correctly.""" + """Tests the switch turns on and off correctly.""" await setup_platform(hass, Platform.SWITCH) - state = hass.states.get("switch.front_siren") - assert state.state == STATE_OFF + assert hass.states.get(entity_id) await hass.services.async_call( SWITCH_DOMAIN, SERVICE_TURN_ON, - {ATTR_ENTITY_ID: "switch.front_siren"}, + {ATTR_ENTITY_ID: entity_id}, blocking=True, ) await hass.async_block_till_done() - state = hass.states.get("switch.front_siren") + state = hass.states.get(entity_id) assert state.state == STATE_ON await hass.services.async_call( SWITCH_DOMAIN, SERVICE_TURN_OFF, - {ATTR_ENTITY_ID: "switch.front_siren"}, + {ATTR_ENTITY_ID: entity_id}, blocking=True, ) await hass.async_block_till_done() - state = hass.states.get("switch.front_siren") + state = hass.states.get(entity_id) assert state.state == STATE_OFF