From 38ea43b678485db952286d9cfb34396084b37b5b Mon Sep 17 00:00:00 2001 From: Andrew Sayre <6730289+andrewsayre@users.noreply.github.com> Date: Sun, 3 Feb 2019 00:08:37 -0600 Subject: [PATCH] Add SmartThings button support via events (#20707) * Add event support for buttons * binary_sensor test clean-up --- .../components/smartthings/__init__.py | 15 ++++++++- homeassistant/components/smartthings/const.py | 2 ++ tests/components/smartthings/conftest.py | 22 ++++++++----- .../smartthings/test_binary_sensor.py | 31 +++++++++++++------ tests/components/smartthings/test_init.py | 31 ++++++++++++++++++- 5 files changed, 82 insertions(+), 19 deletions(-) diff --git a/homeassistant/components/smartthings/__init__.py b/homeassistant/components/smartthings/__init__.py index c705a3df73e..d86524ef62b 100644 --- a/homeassistant/components/smartthings/__init__.py +++ b/homeassistant/components/smartthings/__init__.py @@ -19,7 +19,7 @@ from homeassistant.helpers.typing import ConfigType, HomeAssistantType from .config_flow import SmartThingsFlowHandler # noqa from .const import ( CONF_APP_ID, CONF_INSTALLED_APP_ID, DATA_BROKERS, DATA_MANAGER, DOMAIN, - SIGNAL_SMARTTHINGS_UPDATE, SUPPORTED_PLATFORMS) + EVENT_BUTTON, SIGNAL_SMARTTHINGS_UPDATE, SUPPORTED_PLATFORMS) from .smartapp import ( setup_smartapp, setup_smartapp_endpoint, validate_installed_app) @@ -154,6 +154,19 @@ class DeviceBroker: continue device.status.apply_attribute_update( evt.component_id, evt.capability, evt.attribute, evt.value) + + # Fire events for buttons + if evt.capability == 'button' and evt.attribute == 'button': + data = { + 'component_id': evt.component_id, + 'device_id': evt.device_id, + 'location_id': evt.location_id, + 'value': evt.value, + 'name': device.label + } + self._hass.bus.async_fire(EVENT_BUTTON, data) + _LOGGER.debug("Fired button event: %s", data) + updated_devices.add(device.device_id) _LOGGER.debug("Update received with %s events and updated %s devices", len(req.events), len(updated_devices)) diff --git a/homeassistant/components/smartthings/const.py b/homeassistant/components/smartthings/const.py index f545f84832d..3d0e5cb95f8 100644 --- a/homeassistant/components/smartthings/const.py +++ b/homeassistant/components/smartthings/const.py @@ -12,6 +12,7 @@ CONF_LOCATION_ID = 'location_id' DATA_MANAGER = 'manager' DATA_BROKERS = 'brokers' DOMAIN = 'smartthings' +EVENT_BUTTON = "smartthings.button" SIGNAL_SMARTTHINGS_UPDATE = 'smartthings_update' SIGNAL_SMARTAPP_PREFIX = 'smartthings_smartap_' SETTINGS_INSTANCE_ID = "hassInstanceId" @@ -25,6 +26,7 @@ SUPPORTED_PLATFORMS = [ ] SUPPORTED_CAPABILITIES = [ 'accelerationSensor', + 'button', 'colorControl', 'colorTemperature', 'contactSensor', diff --git a/tests/components/smartthings/conftest.py b/tests/components/smartthings/conftest.py index 56bb5a62888..7358e05f346 100644 --- a/tests/components/smartthings/conftest.py +++ b/tests/components/smartthings/conftest.py @@ -254,14 +254,16 @@ def device_factory_fixture(): @pytest.fixture(name="event_factory") def event_factory_fixture(): """Fixture for creating mock devices.""" - def _factory(device_id, event_type="DEVICE_EVENT"): + def _factory(device_id, event_type="DEVICE_EVENT", capability='', + attribute='Updated', value='Value'): event = Mock() event.event_type = event_type event.device_id = device_id event.component_id = 'main' - event.capability = '' - event.attribute = 'Updated' - event.value = 'Value' + event.capability = capability + event.attribute = attribute + event.value = value + event.location_id = str(uuid4()) return event return _factory @@ -269,11 +271,15 @@ def event_factory_fixture(): @pytest.fixture(name="event_request_factory") def event_request_factory_fixture(event_factory): """Fixture for creating mock smartapp event requests.""" - def _factory(device_ids): + def _factory(device_ids=None, events=None): request = Mock() request.installed_app_id = uuid4() - request.events = [event_factory(id) for id in device_ids] - request.events.append(event_factory(uuid4())) - request.events.append(event_factory(device_ids[0], event_type="OTHER")) + if events is None: + events = [] + if device_ids: + events.extend([event_factory(id) for id in device_ids]) + events.append(event_factory(uuid4())) + events.append(event_factory(device_ids[0], event_type="OTHER")) + request.events = events return request return _factory diff --git a/tests/components/smartthings/test_binary_sensor.py b/tests/components/smartthings/test_binary_sensor.py index 2e0c46842b0..92d891c06d6 100644 --- a/tests/components/smartthings/test_binary_sensor.py +++ b/tests/components/smartthings/test_binary_sensor.py @@ -6,9 +6,10 @@ real HTTP calls are not initiated during testing. """ from pysmartthings import Attribute, Capability +from homeassistant.components.binary_sensor import DEVICE_CLASSES from homeassistant.components.smartthings import DeviceBroker, binary_sensor from homeassistant.components.smartthings.const import ( - DATA_BROKERS, DOMAIN, SIGNAL_SMARTTHINGS_UPDATE) + DATA_BROKERS, DOMAIN, SIGNAL_SMARTTHINGS_UPDATE, SUPPORTED_CAPABILITIES) from homeassistant.config_entries import ( CONN_CLASS_CLOUD_PUSH, SOURCE_USER, ConfigEntry) from homeassistant.const import ATTR_FRIENDLY_NAME @@ -32,6 +33,18 @@ async def _setup_platform(hass, *devices): return config_entry +async def test_mapping_integrity(): + """Test ensures the map dicts have proper integrity.""" + # Ensure every CAPABILITY_TO_ATTRIB key is in SUPPORTED_CAPABILITIES + # Ensure every CAPABILITY_TO_ATTRIB value is in ATTRIB_TO_CLASS keys + for capability, attrib in binary_sensor.CAPABILITY_TO_ATTRIB.items(): + assert capability in SUPPORTED_CAPABILITIES, capability + assert attrib in binary_sensor.ATTRIB_TO_CLASS.keys(), attrib + # Ensure every ATTRIB_TO_CLASS value is in DEVICE_CLASSES + for device_class in binary_sensor.ATTRIB_TO_CLASS.values(): + assert device_class in DEVICE_CLASSES + + async def test_async_setup_platform(): """Test setup platform does nothing (it uses config entries).""" await binary_sensor.async_setup_platform(None, None, None) @@ -58,15 +71,15 @@ async def test_entity_and_device_attributes(hass, device_factory): # Act await _setup_platform(hass, device) # Assert - entity = entity_registry.async_get('binary_sensor.motion_sensor_1_motion') - assert entity - assert entity.unique_id == device.device_id + '.' + Attribute.motion - device_entry = device_registry.async_get_device( + entry = entity_registry.async_get('binary_sensor.motion_sensor_1_motion') + assert entry + assert entry.unique_id == device.device_id + '.' + Attribute.motion + entry = device_registry.async_get_device( {(DOMAIN, device.device_id)}, []) - assert device_entry - assert device_entry.name == device.label - assert device_entry.model == device.device_type_name - assert device_entry.manufacturer == 'Unavailable' + assert entry + assert entry.name == device.label + assert entry.model == device.device_type_name + assert entry.manufacturer == 'Unavailable' async def test_update_from_signal(hass, device_factory): diff --git a/tests/components/smartthings/test_init.py b/tests/components/smartthings/test_init.py index d20d2d4e047..4aef42c1b6f 100644 --- a/tests/components/smartthings/test_init.py +++ b/tests/components/smartthings/test_init.py @@ -8,7 +8,8 @@ import pytest from homeassistant.components import smartthings from homeassistant.components.smartthings.const import ( - DATA_BROKERS, DOMAIN, SIGNAL_SMARTTHINGS_UPDATE, SUPPORTED_PLATFORMS) + DATA_BROKERS, DOMAIN, EVENT_BUTTON, SIGNAL_SMARTTHINGS_UPDATE, + SUPPORTED_PLATFORMS) from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -181,3 +182,31 @@ async def test_event_handler_ignores_other_installed_app( await hass.async_block_till_done() assert not called + + +async def test_event_handler_fires_button_events( + hass, device_factory, event_factory, event_request_factory): + """Test the event handler fires button events.""" + device = device_factory('Button 1', ['button']) + event = event_factory(device.device_id, capability='button', + attribute='button', value='pushed') + request = event_request_factory(events=[event]) + called = False + + def handler(evt): + nonlocal called + called = True + assert evt.data == { + 'component_id': 'main', + 'device_id': device.device_id, + 'location_id': event.location_id, + 'value': 'pushed', + 'name': device.label + } + hass.bus.async_listen(EVENT_BUTTON, handler) + broker = smartthings.DeviceBroker( + hass, [device], request.installed_app_id) + await broker.event_handler(request, None, None) + await hass.async_block_till_done() + + assert called