diff --git a/homeassistant/components/smartthings/__init__.py b/homeassistant/components/smartthings/__init__.py index 535a409bc8d9e3306d790067fb65cf5e59bc58bb..3e0e66e890f5688d1ff2b79a4da66e30c58a61e1 100644 --- a/homeassistant/components/smartthings/__init__.py +++ b/homeassistant/components/smartthings/__init__.py @@ -12,6 +12,7 @@ from pysmartthings import ( Attribute, Capability, Device, + DeviceEvent, Scene, SmartThings, SmartThingsAuthenticationFailedError, @@ -29,7 +30,14 @@ from homeassistant.helpers.config_entry_oauth2_flow import ( async_get_config_entry_implementation, ) -from .const import CONF_INSTALLED_APP_ID, CONF_LOCATION_ID, DOMAIN, MAIN, OLD_DATA +from .const import ( + CONF_INSTALLED_APP_ID, + CONF_LOCATION_ID, + DOMAIN, + EVENT_BUTTON, + MAIN, + OLD_DATA, +) _LOGGER = logging.getLogger(__name__) @@ -141,6 +149,28 @@ async def async_setup_entry(hass: HomeAssistant, entry: SmartThingsConfigEntry) rooms=rooms, ) + def handle_button_press(event: DeviceEvent) -> None: + """Handle a button press.""" + if ( + event.capability is Capability.BUTTON + and event.attribute is Attribute.BUTTON + ): + hass.bus.async_fire( + EVENT_BUTTON, + { + "component_id": event.component_id, + "device_id": event.device_id, + "location_id": event.location_id, + "value": event.value, + "name": entry.runtime_data.devices[event.device_id].device.label, + "data": event.data, + }, + ) + + entry.async_on_unload( + client.add_unspecified_device_event_listener(handle_button_press) + ) + entry.async_create_background_task( hass, client.subscribe( diff --git a/homeassistant/components/smartthings/const.py b/homeassistant/components/smartthings/const.py index 23fd48a4e1e4080d0f677b5183e6c49a71a003fa..a6d028aed06de6b1bce30aa67cbbd6512166342f 100644 --- a/homeassistant/components/smartthings/const.py +++ b/homeassistant/components/smartthings/const.py @@ -32,3 +32,5 @@ CONF_REFRESH_TOKEN = "refresh_token" MAIN = "main" OLD_DATA = "old_data" + +EVENT_BUTTON = "smartthings.button" diff --git a/tests/components/smartthings/__init__.py b/tests/components/smartthings/__init__.py index 6939d3c5dccefbdfc71d971f354bbbf5e21e5c3b..e87d1a8bcdf78eb309714a7ddd1a06c50207d18a 100644 --- a/tests/components/smartthings/__init__.py +++ b/tests/components/smartthings/__init__.py @@ -68,6 +68,8 @@ async def trigger_update( value, data, ) + for call in mock.add_unspecified_device_event_listener.call_args_list: + call[0][0](event) for call in mock.add_device_event_listener.call_args_list: if call[0][0] == device_id: call[0][3](event) diff --git a/tests/components/smartthings/fixtures/device_status/button.json b/tests/components/smartthings/fixtures/device_status/button.json new file mode 100644 index 0000000000000000000000000000000000000000..93e320bcb7b1cbb1cad7f5e31f61c420e46b38d9 --- /dev/null +++ b/tests/components/smartthings/fixtures/device_status/button.json @@ -0,0 +1,21 @@ +{ + "components": { + "main": { + "button": { + "button": { + "value": "pushed", + "timestamp": "2025-03-07T12:20:43.363Z" + }, + "numberOfButtons": { + "value": 1, + "timestamp": "2025-03-07T12:20:43.363Z" + }, + "supportedButtonValues": { + "value": ["pushed", "held", "pushed_2x"], + "timestamp": "2025-03-07T12:20:43.363Z" + } + }, + "refresh": {} + } + } +} diff --git a/tests/components/smartthings/fixtures/devices/button.json b/tests/components/smartthings/fixtures/devices/button.json new file mode 100644 index 0000000000000000000000000000000000000000..ba993ca6aa79710029c061628b5966c9a071dfaf --- /dev/null +++ b/tests/components/smartthings/fixtures/devices/button.json @@ -0,0 +1,49 @@ +{ + "items": [ + { + "deviceId": "c4bdd19f-85d1-4d58-8f9c-e75ac3cf113b", + "name": "button", + "label": "button", + "manufacturerName": "SmartThingsCommunity", + "presentationId": "238c483a-10e8-359b-b032-1be2b2fcdee7", + "locationId": "88a3a314-f0c8-40b4-bb44-44ba06c9c42f", + "ownerId": "12d4af93-cb68-b108-87f5-625437d7371f", + "components": [ + { + "id": "main", + "label": "main", + "capabilities": [ + { + "id": "button", + "version": 1 + }, + { + "id": "refresh", + "version": 1 + } + ], + "categories": [ + { + "name": "Other", + "categoryType": "manufacturer" + } + ] + } + ], + "createTime": "2025-03-07T12:20:43.273Z", + "profile": { + "id": "b045d731-4d01-35bc-8018-b3da711d8904" + }, + "virtual": { + "name": "button", + "executingLocally": false + }, + "type": "VIRTUAL", + "restrictionTier": 0, + "allowed": null, + "executionContext": "CLOUD", + "relationships": [] + } + ], + "_links": {} +} diff --git a/tests/components/smartthings/snapshots/test_init.ambr b/tests/components/smartthings/snapshots/test_init.ambr index 1918f19911a898af08d869b03017028e4ffb4960..5beaf907b70dac28e045fa98c126721d2989eb73 100644 --- a/tests/components/smartthings/snapshots/test_init.ambr +++ b/tests/components/smartthings/snapshots/test_init.ambr @@ -1,4 +1,7 @@ # serializer version: 1 +# name: test_button_event[button] + <Event smartthings.button[L]: component_id=main, device_id=c4bdd19f-85d1-4d58-8f9c-e75ac3cf113b, location_id=abc, value=pushed, name=button, data=None> +# --- # name: test_devices[aeotec_home_energy_meter_gen5] DeviceRegistryEntrySnapshot({ 'area_id': 'toilet', diff --git a/tests/components/smartthings/test_init.py b/tests/components/smartthings/test_init.py index 3ffe2c11a42d851e40a3a03d9851a32754e54447..e3d865fc5c848415e25ae49a04739db407d71e3b 100644 --- a/tests/components/smartthings/test_init.py +++ b/tests/components/smartthings/test_init.py @@ -2,15 +2,16 @@ from unittest.mock import AsyncMock -from pysmartthings import DeviceResponse, DeviceStatus +from pysmartthings import Attribute, Capability, DeviceResponse, DeviceStatus import pytest from syrupy import SnapshotAssertion +from homeassistant.components.smartthings import EVENT_BUTTON from homeassistant.components.smartthings.const import DOMAIN -from homeassistant.core import HomeAssistant +from homeassistant.core import Event, HomeAssistant from homeassistant.helpers import device_registry as dr -from . import setup_integration +from . import setup_integration, trigger_update from tests.common import MockConfigEntry, load_fixture @@ -33,6 +34,35 @@ async def test_devices( assert device == snapshot +@pytest.mark.parametrize("device_fixture", ["button"]) +async def test_button_event( + hass: HomeAssistant, + devices: AsyncMock, + mock_config_entry: MockConfigEntry, + snapshot: SnapshotAssertion, +) -> None: + """Test button event.""" + await setup_integration(hass, mock_config_entry) + events = [] + + def capture_event(event: Event) -> None: + events.append(event) + + hass.bus.async_listen_once(EVENT_BUTTON, capture_event) + + await trigger_update( + hass, + devices, + "c4bdd19f-85d1-4d58-8f9c-e75ac3cf113b", + Capability.BUTTON, + Attribute.BUTTON, + "pushed", + ) + + assert len(events) == 1 + assert events[0] == snapshot + + @pytest.mark.parametrize("device_fixture", ["da_ac_rac_000001"]) async def test_removing_stale_devices( hass: HomeAssistant,