core/homeassistant/components/ring/switch.py

151 lines
5.4 KiB
Python

"""Component providing HA switch support for Ring Door Bell/Chimes."""
from collections.abc import Callable, Coroutine, Sequence
from dataclasses import dataclass
import logging
from typing import Any, Generic, Self, cast
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
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
import homeassistant.util.dt as dt_util
from . import RingConfigEntry
from .coordinator import RingDataCoordinator
from .entity import (
DeprecatedInfo,
RingDeviceT,
RingEntity,
RingEntityDescription,
async_check_create_deprecated,
refresh_after,
)
_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(
SwitchEntityDescription, RingEntityDescription, Generic[RingDeviceT]
):
"""Describes a Ring switch entity."""
exists_fn: Callable[[RingDeviceT], bool]
unique_id_fn: Callable[[Self, RingDeviceT], str] = (
lambda self, device: f"{device.device_api_id}-{self.key}"
)
is_on_fn: Callable[[RingDeviceT], bool]
turn_on_fn: Callable[[RingDeviceT], Coroutine[Any, Any, None]]
turn_off_fn: Callable[[RingDeviceT], Coroutine[Any, Any, None]]
SWITCHES: Sequence[RingSwitchEntityDescription[Any]] = (
RingSwitchEntityDescription[RingStickUpCam](
key="siren",
translation_key="siren",
exists_fn=lambda device: device.has_capability(RingCapability.SIREN),
is_on_fn=lambda device: device.siren > 0,
turn_on_fn=lambda device: device.async_set_siren(1),
turn_off_fn=lambda device: device.async_set_siren(0),
deprecated_info=DeprecatedInfo(
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
),
),
RingSwitchEntityDescription[RingDoorBell](
key="motion_detection",
translation_key="motion_detection",
exists_fn=lambda device: device.has_capability(RingCapability.MOTION_DETECTION),
is_on_fn=lambda device: device.motion_detection,
turn_on_fn=lambda device: device.async_set_motion_detection(True),
turn_off_fn=lambda device: device.async_set_motion_detection(False),
),
)
async def async_setup_entry(
hass: HomeAssistant,
entry: RingConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Create the switches for the Ring devices."""
ring_data = entry.runtime_data
devices_coordinator = ring_data.devices_coordinator
async_add_entities(
RingSwitch(device, devices_coordinator, description)
for description in SWITCHES
for device in ring_data.devices.all_devices
if description.exists_fn(device)
and async_check_create_deprecated(
hass,
Platform.SWITCH,
description.unique_id_fn(description, device),
description,
)
)
class RingSwitch(RingEntity[RingDeviceT], SwitchEntity):
"""Represents a switch for controlling an aspect of a ring device."""
entity_description: RingSwitchEntityDescription[RingDeviceT]
def __init__(
self,
device: RingDeviceT,
coordinator: RingDataCoordinator,
description: RingSwitchEntityDescription[RingDeviceT],
) -> None:
"""Initialize the switch."""
super().__init__(device, coordinator)
self.entity_description = description
self._no_updates_until = dt_util.utcnow()
self._attr_unique_id = description.unique_id_fn(description, device)
self._attr_is_on = description.is_on_fn(device)
@callback
def _handle_coordinator_update(self) -> None:
"""Call update method."""
self._device = cast(
RingDeviceT,
self._get_coordinator_data().get_device(self._device.device_api_id),
)
self._attr_is_on = self.entity_description.is_on_fn(self._device)
super()._handle_coordinator_update()
@refresh_after
async def _async_set_switch(self, switch_on: bool) -> None:
"""Update switch state, and causes Home Assistant to correctly update."""
if switch_on:
await self.entity_description.turn_on_fn(self._device)
else:
await self.entity_description.turn_off_fn(self._device)
self._attr_is_on = switch_on
self.async_write_ha_state()
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the siren on for 30 seconds."""
await self._async_set_switch(True)
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the siren off."""
await self._async_set_switch(False)