Add SmartThings button support via events (#20707)

* Add event support for buttons

* binary_sensor test clean-up
This commit is contained in:
Andrew Sayre 2019-02-03 00:08:37 -06:00 committed by Paulus Schoutsen
parent 3553d26f6b
commit 38ea43b678
5 changed files with 82 additions and 19 deletions

View file

@ -19,7 +19,7 @@ from homeassistant.helpers.typing import ConfigType, HomeAssistantType
from .config_flow import SmartThingsFlowHandler # noqa from .config_flow import SmartThingsFlowHandler # noqa
from .const import ( from .const import (
CONF_APP_ID, CONF_INSTALLED_APP_ID, DATA_BROKERS, DATA_MANAGER, DOMAIN, 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 ( from .smartapp import (
setup_smartapp, setup_smartapp_endpoint, validate_installed_app) setup_smartapp, setup_smartapp_endpoint, validate_installed_app)
@ -154,6 +154,19 @@ class DeviceBroker:
continue continue
device.status.apply_attribute_update( device.status.apply_attribute_update(
evt.component_id, evt.capability, evt.attribute, evt.value) 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) updated_devices.add(device.device_id)
_LOGGER.debug("Update received with %s events and updated %s devices", _LOGGER.debug("Update received with %s events and updated %s devices",
len(req.events), len(updated_devices)) len(req.events), len(updated_devices))

View file

@ -12,6 +12,7 @@ CONF_LOCATION_ID = 'location_id'
DATA_MANAGER = 'manager' DATA_MANAGER = 'manager'
DATA_BROKERS = 'brokers' DATA_BROKERS = 'brokers'
DOMAIN = 'smartthings' DOMAIN = 'smartthings'
EVENT_BUTTON = "smartthings.button"
SIGNAL_SMARTTHINGS_UPDATE = 'smartthings_update' SIGNAL_SMARTTHINGS_UPDATE = 'smartthings_update'
SIGNAL_SMARTAPP_PREFIX = 'smartthings_smartap_' SIGNAL_SMARTAPP_PREFIX = 'smartthings_smartap_'
SETTINGS_INSTANCE_ID = "hassInstanceId" SETTINGS_INSTANCE_ID = "hassInstanceId"
@ -25,6 +26,7 @@ SUPPORTED_PLATFORMS = [
] ]
SUPPORTED_CAPABILITIES = [ SUPPORTED_CAPABILITIES = [
'accelerationSensor', 'accelerationSensor',
'button',
'colorControl', 'colorControl',
'colorTemperature', 'colorTemperature',
'contactSensor', 'contactSensor',

View file

@ -254,14 +254,16 @@ def device_factory_fixture():
@pytest.fixture(name="event_factory") @pytest.fixture(name="event_factory")
def event_factory_fixture(): def event_factory_fixture():
"""Fixture for creating mock devices.""" """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 = Mock()
event.event_type = event_type event.event_type = event_type
event.device_id = device_id event.device_id = device_id
event.component_id = 'main' event.component_id = 'main'
event.capability = '' event.capability = capability
event.attribute = 'Updated' event.attribute = attribute
event.value = 'Value' event.value = value
event.location_id = str(uuid4())
return event return event
return _factory return _factory
@ -269,11 +271,15 @@ def event_factory_fixture():
@pytest.fixture(name="event_request_factory") @pytest.fixture(name="event_request_factory")
def event_request_factory_fixture(event_factory): def event_request_factory_fixture(event_factory):
"""Fixture for creating mock smartapp event requests.""" """Fixture for creating mock smartapp event requests."""
def _factory(device_ids): def _factory(device_ids=None, events=None):
request = Mock() request = Mock()
request.installed_app_id = uuid4() request.installed_app_id = uuid4()
request.events = [event_factory(id) for id in device_ids] if events is None:
request.events.append(event_factory(uuid4())) events = []
request.events.append(event_factory(device_ids[0], event_type="OTHER")) 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 request
return _factory return _factory

View file

@ -6,9 +6,10 @@ real HTTP calls are not initiated during testing.
""" """
from pysmartthings import Attribute, Capability 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 import DeviceBroker, binary_sensor
from homeassistant.components.smartthings.const import ( 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 ( from homeassistant.config_entries import (
CONN_CLASS_CLOUD_PUSH, SOURCE_USER, ConfigEntry) CONN_CLASS_CLOUD_PUSH, SOURCE_USER, ConfigEntry)
from homeassistant.const import ATTR_FRIENDLY_NAME from homeassistant.const import ATTR_FRIENDLY_NAME
@ -32,6 +33,18 @@ async def _setup_platform(hass, *devices):
return config_entry 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(): async def test_async_setup_platform():
"""Test setup platform does nothing (it uses config entries).""" """Test setup platform does nothing (it uses config entries)."""
await binary_sensor.async_setup_platform(None, None, None) await binary_sensor.async_setup_platform(None, None, None)
@ -58,15 +71,15 @@ async def test_entity_and_device_attributes(hass, device_factory):
# Act # Act
await _setup_platform(hass, device) await _setup_platform(hass, device)
# Assert # Assert
entity = entity_registry.async_get('binary_sensor.motion_sensor_1_motion') entry = entity_registry.async_get('binary_sensor.motion_sensor_1_motion')
assert entity assert entry
assert entity.unique_id == device.device_id + '.' + Attribute.motion assert entry.unique_id == device.device_id + '.' + Attribute.motion
device_entry = device_registry.async_get_device( entry = device_registry.async_get_device(
{(DOMAIN, device.device_id)}, []) {(DOMAIN, device.device_id)}, [])
assert device_entry assert entry
assert device_entry.name == device.label assert entry.name == device.label
assert device_entry.model == device.device_type_name assert entry.model == device.device_type_name
assert device_entry.manufacturer == 'Unavailable' assert entry.manufacturer == 'Unavailable'
async def test_update_from_signal(hass, device_factory): async def test_update_from_signal(hass, device_factory):

View file

@ -8,7 +8,8 @@ import pytest
from homeassistant.components import smartthings from homeassistant.components import smartthings
from homeassistant.components.smartthings.const import ( 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.exceptions import ConfigEntryNotReady
from homeassistant.helpers.dispatcher import async_dispatcher_connect 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() await hass.async_block_till_done()
assert not called 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