diff --git a/homeassistant/components/tailwind/__init__.py b/homeassistant/components/tailwind/__init__.py index 2691e9c36e8..8f8ef4134a9 100644 --- a/homeassistant/components/tailwind/__init__.py +++ b/homeassistant/components/tailwind/__init__.py @@ -8,7 +8,7 @@ from homeassistant.core import HomeAssistant from .const import DOMAIN from .coordinator import TailwindDataUpdateCoordinator -PLATFORMS = [Platform.NUMBER] +PLATFORMS = [Platform.BUTTON, Platform.NUMBER] async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: diff --git a/homeassistant/components/tailwind/button.py b/homeassistant/components/tailwind/button.py new file mode 100644 index 00000000000..d860f7de3d6 --- /dev/null +++ b/homeassistant/components/tailwind/button.py @@ -0,0 +1,75 @@ +"""Button entity platform for Tailwind.""" +from __future__ import annotations + +from collections.abc import Awaitable, Callable +from dataclasses import dataclass +from typing import Any + +from gotailwind import Tailwind + +from homeassistant.components.button import ( + ButtonDeviceClass, + ButtonEntity, + ButtonEntityDescription, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import EntityCategory +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from .const import DOMAIN +from .coordinator import TailwindDataUpdateCoordinator +from .entity import TailwindEntity + + +@dataclass(kw_only=True) +class TailwindButtonEntityDescription(ButtonEntityDescription): + """Class describing Tailwind button entities.""" + + press_fn: Callable[[Tailwind], Awaitable[Any]] + + +DESCRIPTIONS = [ + TailwindButtonEntityDescription( + key="identify", + device_class=ButtonDeviceClass.IDENTIFY, + entity_category=EntityCategory.CONFIG, + press_fn=lambda tailwind: tailwind.identify(), + ), +] + + +async def async_setup_entry( + hass: HomeAssistant, + entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up Tailwind button based on a config entry.""" + coordinator: TailwindDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] + async_add_entities( + TailwindButtonEntity( + coordinator, + description, + ) + for description in DESCRIPTIONS + ) + + +class TailwindButtonEntity(TailwindEntity, ButtonEntity): + """Representation of a Tailwind button entity.""" + + entity_description: TailwindButtonEntityDescription + + def __init__( + self, + coordinator: TailwindDataUpdateCoordinator, + description: TailwindButtonEntityDescription, + ) -> None: + """Initiate Tailwind button entity.""" + super().__init__(coordinator=coordinator) + self.entity_description = description + self._attr_unique_id = f"{coordinator.data.device_id}-{description.key}" + + async def async_press(self) -> None: + """Trigger button press on the Tailwind device.""" + await self.entity_description.press_fn(self.coordinator.tailwind) diff --git a/tests/components/tailwind/snapshots/test_button.ambr b/tests/components/tailwind/snapshots/test_button.ambr new file mode 100644 index 00000000000..b92b482e23d --- /dev/null +++ b/tests/components/tailwind/snapshots/test_button.ambr @@ -0,0 +1,77 @@ +# serializer version: 1 +# name: test_number_entities + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'identify', + 'friendly_name': 'Tailwind iQ3 Identify', + }), + 'context': , + 'entity_id': 'button.tailwind_iq3_identify', + 'last_changed': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_number_entities.1 + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'button', + 'entity_category': , + 'entity_id': 'button.tailwind_iq3_identify', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Identify', + 'platform': 'tailwind', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': '_3c_e9_e_6d_21_84_-identify', + 'unit_of_measurement': None, + }) +# --- +# name: test_number_entities.2 + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entries': , + 'configuration_url': None, + 'connections': set({ + tuple( + 'mac', + '3c:e9:0e:6d:21:84', + ), + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + 'identifiers': set({ + tuple( + 'tailwind', + '_3c_e9_e_6d_21_84_', + ), + }), + 'is_new': False, + 'manufacturer': 'Tailwind', + 'model': 'iQ3', + 'name': 'Tailwind iQ3', + 'name_by_user': None, + 'serial_number': None, + 'suggested_area': None, + 'sw_version': '10.10', + 'via_device_id': None, + }) +# --- diff --git a/tests/components/tailwind/test_button.py b/tests/components/tailwind/test_button.py new file mode 100644 index 00000000000..708816d733c --- /dev/null +++ b/tests/components/tailwind/test_button.py @@ -0,0 +1,48 @@ +"""Tests for button entities provided by the Tailwind integration.""" +from unittest.mock import MagicMock + +import pytest +from syrupy.assertion import SnapshotAssertion + +from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN, SERVICE_PRESS +from homeassistant.const import ATTR_ENTITY_ID +from homeassistant.core import HomeAssistant +from homeassistant.helpers import device_registry as dr, entity_registry as er + +pytestmark = [ + pytest.mark.usefixtures("init_integration"), + pytest.mark.freeze_time("2023-12-17 15:25:00"), +] + + +async def test_number_entities( + hass: HomeAssistant, + device_registry: dr.DeviceRegistry, + entity_registry: er.EntityRegistry, + mock_tailwind: MagicMock, + snapshot: SnapshotAssertion, +) -> None: + """Test button entities provided by the Tailwind integration.""" + assert (state := hass.states.get("button.tailwind_iq3_identify")) + assert snapshot == state + + assert (entity_entry := entity_registry.async_get(state.entity_id)) + assert snapshot == entity_entry + + assert entity_entry.device_id + assert (device_entry := device_registry.async_get(entity_entry.device_id)) + assert snapshot == device_entry + + assert len(mock_tailwind.identify.mock_calls) == 0 + await hass.services.async_call( + BUTTON_DOMAIN, + SERVICE_PRESS, + {ATTR_ENTITY_ID: state.entity_id}, + blocking=True, + ) + + assert len(mock_tailwind.identify.mock_calls) == 1 + mock_tailwind.identify.assert_called_with() + + assert (state := hass.states.get(state.entity_id)) + assert state.state == "2023-12-17T15:25:00+00:00"