diff --git a/homeassistant/components/snoo/__init__.py b/homeassistant/components/snoo/__init__.py index 20d94be7c033..bf4dc07f96c6 100644 --- a/homeassistant/components/snoo/__init__.py +++ b/homeassistant/components/snoo/__init__.py @@ -19,6 +19,7 @@ _LOGGER = logging.getLogger(__name__) PLATFORMS: list[Platform] = [ Platform.BINARY_SENSOR, + Platform.BUTTON, Platform.EVENT, Platform.SELECT, Platform.SENSOR, diff --git a/homeassistant/components/snoo/button.py b/homeassistant/components/snoo/button.py new file mode 100644 index 000000000000..c7faabb142f5 --- /dev/null +++ b/homeassistant/components/snoo/button.py @@ -0,0 +1,69 @@ +"""Support for Snoo Buttons.""" + +from collections.abc import Awaitable, Callable +from dataclasses import dataclass + +from python_snoo.containers import SnooDevice +from python_snoo.exceptions import SnooCommandException +from python_snoo.snoo import Snoo + +from homeassistant.components.button import ButtonEntity, ButtonEntityDescription +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback + +from .const import DOMAIN +from .coordinator import SnooConfigEntry +from .entity import SnooDescriptionEntity + + +@dataclass(kw_only=True, frozen=True) +class SnooButtonEntityDescription(ButtonEntityDescription): + """Description for Snoo button entities.""" + + press_fn: Callable[[Snoo, SnooDevice], Awaitable[None]] + + +BUTTON_DESCRIPTIONS: list[SnooButtonEntityDescription] = [ + SnooButtonEntityDescription( + key="start_snoo", + translation_key="start_snoo", + press_fn=lambda snoo, device: snoo.start_snoo( + device, + ), + ), +] + + +async def async_setup_entry( + hass: HomeAssistant, + entry: SnooConfigEntry, + async_add_entities: AddConfigEntryEntitiesCallback, +) -> None: + """Set up buttons for Snoo device.""" + coordinators = entry.runtime_data + async_add_entities( + SnooButton(coordinator, description) + for coordinator in coordinators.values() + for description in BUTTON_DESCRIPTIONS + ) + + +class SnooButton(SnooDescriptionEntity, ButtonEntity): + """Representation of a Snoo button.""" + + entity_description: SnooButtonEntityDescription + + async def async_press(self) -> None: + """Handle the button press.""" + try: + await self.entity_description.press_fn( + self.coordinator.snoo, + self.coordinator.device, + ) + except SnooCommandException as err: + raise HomeAssistantError( + translation_domain=DOMAIN, + translation_key=f"{self.entity_description.key}_failed", + translation_placeholders={"name": str(self.name)}, + ) from err diff --git a/homeassistant/components/snoo/icons.json b/homeassistant/components/snoo/icons.json new file mode 100644 index 000000000000..44504a4c969a --- /dev/null +++ b/homeassistant/components/snoo/icons.json @@ -0,0 +1,9 @@ +{ + "entity": { + "button": { + "start_snoo": { + "default": "mdi:play" + } + } + } +} diff --git a/homeassistant/components/snoo/strings.json b/homeassistant/components/snoo/strings.json index e4a5c634a68c..c86e0dd89073 100644 --- a/homeassistant/components/snoo/strings.json +++ b/homeassistant/components/snoo/strings.json @@ -25,6 +25,9 @@ "select_failed": { "message": "Error while updating {name} to {option}" }, + "start_snoo_failed": { + "message": "Starting {name} failed" + }, "switch_on_failed": { "message": "Turning {name} on failed" }, @@ -41,6 +44,11 @@ "name": "Right safety clip" } }, + "button": { + "start_snoo": { + "name": "Start" + } + }, "event": { "event": { "name": "Snoo event", diff --git a/tests/components/snoo/snapshots/test_button.ambr b/tests/components/snoo/snapshots/test_button.ambr new file mode 100644 index 000000000000..05920b09105e --- /dev/null +++ b/tests/components/snoo/snapshots/test_button.ambr @@ -0,0 +1,49 @@ +# serializer version: 1 +# name: test_entities[button.test_snoo_start-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'button', + 'entity_category': None, + 'entity_id': 'button.test_snoo_start', + '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': 'Start', + 'platform': 'snoo', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'start_snoo', + 'unique_id': 'random_num_start_snoo', + 'unit_of_measurement': None, + }) +# --- +# name: test_entities[button.test_snoo_start-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Test Snoo Start', + }), + 'context': , + 'entity_id': 'button.test_snoo_start', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unavailable', + }) +# --- diff --git a/tests/components/snoo/test_button.py b/tests/components/snoo/test_button.py new file mode 100644 index 000000000000..84705f9e6fdf --- /dev/null +++ b/tests/components/snoo/test_button.py @@ -0,0 +1,41 @@ +"""Test Snoo Buttons.""" + +from unittest.mock import AsyncMock, patch + +from syrupy.assertion import SnapshotAssertion + +from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN, SERVICE_PRESS +from homeassistant.const import ATTR_ENTITY_ID, Platform +from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er + +from . import async_init_integration + +from tests.common import snapshot_platform + + +async def test_entities( + hass: HomeAssistant, + bypass_api: AsyncMock, + snapshot: SnapshotAssertion, + entity_registry: er.EntityRegistry, +) -> None: + """Test buttons.""" + with patch("homeassistant.components.snoo.PLATFORMS", [Platform.BUTTON]): + entry = await async_init_integration(hass) + + await snapshot_platform(hass, entity_registry, snapshot, entry.entry_id) + + +async def test_button_starts_snoo(hass: HomeAssistant, bypass_api: AsyncMock) -> None: + """Test start_snoo button works correctly.""" + await async_init_integration(hass) + + await hass.services.async_call( + BUTTON_DOMAIN, + SERVICE_PRESS, + {ATTR_ENTITY_ID: "button.test_snoo_start"}, + blocking=True, + ) + + assert bypass_api.start_snoo.assert_called_once