diff --git a/homeassistant/components/smartthings/__init__.py b/homeassistant/components/smartthings/__init__.py index 31309b73a66..e5351798219 100644 --- a/homeassistant/components/smartthings/__init__.py +++ b/homeassistant/components/smartthings/__init__.py @@ -79,6 +79,7 @@ type SmartThingsConfigEntry = ConfigEntry[SmartThingsData] PLATFORMS = [ Platform.BINARY_SENSOR, + Platform.BUTTON, Platform.CLIMATE, Platform.COVER, Platform.EVENT, diff --git a/homeassistant/components/smartthings/button.py b/homeassistant/components/smartthings/button.py new file mode 100644 index 00000000000..ad61880f3b1 --- /dev/null +++ b/homeassistant/components/smartthings/button.py @@ -0,0 +1,75 @@ +"""Support for button entities through the SmartThings cloud API.""" + +from __future__ import annotations + +from dataclasses import dataclass + +from pysmartthings import Capability, Command, SmartThings + +from homeassistant.components.button import ButtonEntity, ButtonEntityDescription +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback + +from . import FullDevice, SmartThingsConfigEntry +from .const import MAIN +from .entity import SmartThingsEntity + + +@dataclass(frozen=True, kw_only=True) +class SmartThingsButtonDescription(ButtonEntityDescription): + """Class describing SmartThings button entities.""" + + key: Capability + command: Command + + +CAPABILITIES_TO_BUTTONS: dict[Capability | str, SmartThingsButtonDescription] = { + Capability.OVEN_OPERATING_STATE: SmartThingsButtonDescription( + key=Capability.OVEN_OPERATING_STATE, + translation_key="stop", + command=Command.STOP, + ), +} + + +async def async_setup_entry( + hass: HomeAssistant, + entry: SmartThingsConfigEntry, + async_add_entities: AddConfigEntryEntitiesCallback, +) -> None: + """Add button entities for a config entry.""" + entry_data = entry.runtime_data + async_add_entities( + SmartThingsButtonEntity( + entry_data.client, device, CAPABILITIES_TO_BUTTONS[capability] + ) + for device in entry_data.devices.values() + for capability in device.status[MAIN] + if capability in CAPABILITIES_TO_BUTTONS + ) + + +class SmartThingsButtonEntity(SmartThingsEntity, ButtonEntity): + """Define a SmartThings button.""" + + entity_description: SmartThingsButtonDescription + + def __init__( + self, + client: SmartThings, + device: FullDevice, + entity_description: SmartThingsButtonDescription, + ) -> None: + """Initialize the instance.""" + super().__init__(client, device, set()) + self.entity_description = entity_description + self._attr_unique_id = ( + f"{device.device.device_id}_{MAIN}_{entity_description.key}" + ) + + async def async_press(self) -> None: + """Press the button.""" + await self.execute_device_command( + self.entity_description.key, + self.entity_description.command, + ) diff --git a/homeassistant/components/smartthings/icons.json b/homeassistant/components/smartthings/icons.json index 9cfdb8da7ec..80ac70edc3f 100644 --- a/homeassistant/components/smartthings/icons.json +++ b/homeassistant/components/smartthings/icons.json @@ -14,6 +14,11 @@ } } }, + "button": { + "stop": { + "default": "mdi:stop" + } + }, "number": { "washer_rinse_cycles": { "default": "mdi:waves-arrow-up" diff --git a/homeassistant/components/smartthings/strings.json b/homeassistant/components/smartthings/strings.json index 9616c97fbe1..13f4a6a2831 100644 --- a/homeassistant/components/smartthings/strings.json +++ b/homeassistant/components/smartthings/strings.json @@ -46,6 +46,11 @@ "name": "Valve" } }, + "button": { + "stop": { + "name": "Stop" + } + }, "event": { "button": { "state": { diff --git a/tests/components/smartthings/snapshots/test_button.ambr b/tests/components/smartthings/snapshots/test_button.ambr new file mode 100644 index 00000000000..a16ad794929 --- /dev/null +++ b/tests/components/smartthings/snapshots/test_button.ambr @@ -0,0 +1,142 @@ +# serializer version: 1 +# name: test_all_entities[da_ks_microwave_0101x][button.microwave_stop-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.microwave_stop', + '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': 'Stop', + 'platform': 'smartthings', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'stop', + 'unique_id': '2bad3237-4886-e699-1b90-4a51a3d55c8a_main_ovenOperatingState', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[da_ks_microwave_0101x][button.microwave_stop-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Microwave Stop', + }), + 'context': , + 'entity_id': 'button.microwave_stop', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_all_entities[da_ks_oven_01061][button.oven_stop-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.oven_stop', + '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': 'Stop', + 'platform': 'smartthings', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'stop', + 'unique_id': '9447959a-0dfa-6b27-d40d-650da525c53f_main_ovenOperatingState', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[da_ks_oven_01061][button.oven_stop-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Oven Stop', + }), + 'context': , + 'entity_id': 'button.oven_stop', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_all_entities[da_ks_range_0101x][button.vulcan_stop-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.vulcan_stop', + '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': 'Stop', + 'platform': 'smartthings', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'stop', + 'unique_id': '2c3cbaa0-1899-5ddc-7b58-9d657bd48f18_main_ovenOperatingState', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[da_ks_range_0101x][button.vulcan_stop-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Vulcan Stop', + }), + 'context': , + 'entity_id': 'button.vulcan_stop', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- diff --git a/tests/components/smartthings/test_button.py b/tests/components/smartthings/test_button.py new file mode 100644 index 00000000000..4a348d079ca --- /dev/null +++ b/tests/components/smartthings/test_button.py @@ -0,0 +1,56 @@ +"""Test for the SmartThings button platform.""" + +from unittest.mock import AsyncMock + +from freezegun.api import FrozenDateTimeFactory +from pysmartthings import Capability, Command +import pytest +from syrupy import SnapshotAssertion + +from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN, SERVICE_PRESS +from homeassistant.components.smartthings import MAIN +from homeassistant.const import ATTR_ENTITY_ID, Platform +from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er + +from . import setup_integration, snapshot_smartthings_entities + +from tests.common import MockConfigEntry + + +async def test_all_entities( + hass: HomeAssistant, + snapshot: SnapshotAssertion, + devices: AsyncMock, + mock_config_entry: MockConfigEntry, + entity_registry: er.EntityRegistry, +) -> None: + """Test all entities.""" + await setup_integration(hass, mock_config_entry) + + snapshot_smartthings_entities(hass, entity_registry, snapshot, Platform.BUTTON) + + +@pytest.mark.parametrize("device_fixture", ["da_ks_microwave_0101x"]) +async def test_press( + hass: HomeAssistant, + devices: AsyncMock, + mock_config_entry: MockConfigEntry, + freezer: FrozenDateTimeFactory, +) -> None: + """Test state update.""" + await setup_integration(hass, mock_config_entry) + freezer.move_to("2023-10-21") + + await hass.services.async_call( + BUTTON_DOMAIN, + SERVICE_PRESS, + {ATTR_ENTITY_ID: "button.microwave_stop"}, + blocking=True, + ) + devices.execute_device_command.assert_called_once_with( + "2bad3237-4886-e699-1b90-4a51a3d55c8a", + Capability.OVEN_OPERATING_STATE, + Command.STOP, + MAIN, + )