Fire events when Google Assistant commands come in #15139 (#20204)

This commit is contained in:
Penny Wood 2019-02-28 03:33:34 +08:00 committed by Paulus Schoutsen
parent 9b3a3fc1ac
commit b87eb9d79e
4 changed files with 134 additions and 9 deletions

View File

@ -22,6 +22,8 @@ from .const import (
CONF_EXPOSE, CONF_ALIASES, CONF_ROOM_HINT, CONF_ALLOW_UNLOCK,
DEFAULT_ALLOW_UNLOCK
)
from .const import EVENT_COMMAND_RECEIVED, EVENT_SYNC_RECEIVED # noqa: F401
from .const import EVENT_QUERY_RECEIVED # noqa: F401
from .http import async_register_http
_LOGGER = logging.getLogger(__name__)

View File

@ -42,3 +42,8 @@ ERR_NOT_SUPPORTED = "notSupported"
ERR_PROTOCOL_ERROR = 'protocolError'
ERR_UNKNOWN_ERROR = 'unknownError'
ERR_FUNCTION_NOT_SUPPORTED = 'functionNotSupported'
# Event types
EVENT_COMMAND_RECEIVED = 'google_assistant_command_received'
EVENT_QUERY_RECEIVED = 'google_assistant_query_received'
EVENT_SYNC_RECEIVED = 'google_assistant_sync_received'

View File

@ -8,7 +8,7 @@ from homeassistant.util.decorator import Registry
from homeassistant.core import callback
from homeassistant.const import (
CLOUD_NEVER_EXPOSED_ENTITIES, CONF_NAME, STATE_UNAVAILABLE,
ATTR_SUPPORTED_FEATURES
ATTR_SUPPORTED_FEATURES, ATTR_ENTITY_ID,
)
from homeassistant.components import (
climate,
@ -32,7 +32,8 @@ from .const import (
TYPE_THERMOSTAT, TYPE_FAN,
CONF_ALIASES, CONF_ROOM_HINT,
ERR_FUNCTION_NOT_SUPPORTED, ERR_PROTOCOL_ERROR, ERR_DEVICE_OFFLINE,
ERR_UNKNOWN_ERROR
ERR_UNKNOWN_ERROR,
EVENT_COMMAND_RECEIVED, EVENT_SYNC_RECEIVED, EVENT_QUERY_RECEIVED
)
from .helpers import SmartHomeError
@ -214,7 +215,8 @@ async def _process(hass, config, message):
}
try:
result = await handler(hass, config, inputs[0].get('payload'))
result = await handler(hass, config, request_id,
inputs[0].get('payload'))
except SmartHomeError as err:
return {
'requestId': request_id,
@ -233,11 +235,15 @@ async def _process(hass, config, message):
@HANDLERS.register('action.devices.SYNC')
async def async_devices_sync(hass, config, payload):
async def async_devices_sync(hass, config, request_id, payload):
"""Handle action.devices.SYNC request.
https://developers.google.com/actions/smarthome/create-app#actiondevicessync
"""
hass.bus.async_fire(EVENT_SYNC_RECEIVED, {
'request_id': request_id
})
devices = []
for state in hass.states.async_all():
if state.entity_id in CLOUD_NEVER_EXPOSED_ENTITIES:
@ -255,14 +261,16 @@ async def async_devices_sync(hass, config, payload):
devices.append(serialized)
return {
response = {
'agentUserId': config.agent_user_id,
'devices': devices,
}
return response
@HANDLERS.register('action.devices.QUERY')
async def async_devices_query(hass, config, payload):
async def async_devices_query(hass, config, request_id, payload):
"""Handle action.devices.QUERY request.
https://developers.google.com/actions/smarthome/create-app#actiondevicesquery
@ -272,6 +280,11 @@ async def async_devices_query(hass, config, payload):
devid = device['id']
state = hass.states.get(devid)
hass.bus.async_fire(EVENT_QUERY_RECEIVED, {
'request_id': request_id,
ATTR_ENTITY_ID: devid,
})
if not state:
# If we can't find a state, the device is offline
devices[devid] = {'online': False}
@ -283,7 +296,7 @@ async def async_devices_query(hass, config, payload):
@HANDLERS.register('action.devices.EXECUTE')
async def handle_devices_execute(hass, config, payload):
async def handle_devices_execute(hass, config, request_id, payload):
"""Handle action.devices.EXECUTE request.
https://developers.google.com/actions/smarthome/create-app#actiondevicesexecute
@ -296,6 +309,12 @@ async def handle_devices_execute(hass, config, payload):
command['execution']):
entity_id = device['id']
hass.bus.async_fire(EVENT_COMMAND_RECEIVED, {
'request_id': request_id,
ATTR_ENTITY_ID: entity_id,
'execution': execution
})
# Happens if error occurred. Skip entity for further processing
if entity_id in results:
continue
@ -341,7 +360,7 @@ async def handle_devices_execute(hass, config, payload):
@HANDLERS.register('action.devices.DISCONNECT')
async def async_devices_disconnect(hass, config, payload):
async def async_devices_disconnect(hass, config, request_id, payload):
"""Handle action.devices.DISCONNECT request.
https://developers.google.com/actions/smarthome/create#actiondevicesdisconnect

View File

@ -7,7 +7,8 @@ from homeassistant.components.climate.const import (
ATTR_MIN_TEMP, ATTR_MAX_TEMP, STATE_HEAT, SUPPORT_OPERATION_MODE
)
from homeassistant.components.google_assistant import (
const, trait, helpers, smart_home as sh)
const, trait, helpers, smart_home as sh,
EVENT_COMMAND_RECEIVED, EVENT_QUERY_RECEIVED, EVENT_SYNC_RECEIVED)
from homeassistant.components.light.demo import DemoLight
@ -48,6 +49,9 @@ async def test_sync_message(hass):
}
)
events = []
hass.bus.async_listen(EVENT_SYNC_RECEIVED, events.append)
result = await sh.async_handle_message(hass, config, {
"requestId": REQ_ID,
"inputs": [{
@ -85,6 +89,13 @@ async def test_sync_message(hass):
}]
}
}
await hass.async_block_till_done()
assert len(events) == 1
assert events[0].event_type == EVENT_SYNC_RECEIVED
assert events[0].data == {
'request_id': REQ_ID,
}
async def test_query_message(hass):
@ -109,6 +120,9 @@ async def test_query_message(hass):
light2.entity_id = 'light.another_light'
await light2.async_update_ha_state()
events = []
hass.bus.async_listen(EVENT_QUERY_RECEIVED, events.append)
result = await sh.async_handle_message(hass, BASIC_CONFIG, {
"requestId": REQ_ID,
"inputs": [{
@ -149,12 +163,33 @@ async def test_query_message(hass):
}
}
assert len(events) == 3
assert events[0].event_type == EVENT_QUERY_RECEIVED
assert events[0].data == {
'request_id': REQ_ID,
'entity_id': 'light.demo_light'
}
assert events[1].event_type == EVENT_QUERY_RECEIVED
assert events[1].data == {
'request_id': REQ_ID,
'entity_id': 'light.another_light'
}
assert events[2].event_type == EVENT_QUERY_RECEIVED
assert events[2].data == {
'request_id': REQ_ID,
'entity_id': 'light.non_existing'
}
async def test_execute(hass):
"""Test an execute command."""
await async_setup_component(hass, 'light', {
'light': {'platform': 'demo'}
})
events = []
hass.bus.async_listen(EVENT_COMMAND_RECEIVED, events.append)
await hass.services.async_call(
'light', 'turn_off', {'entity_id': 'light.ceiling_lights'},
blocking=True)
@ -209,6 +244,52 @@ async def test_execute(hass):
}
}
assert len(events) == 4
assert events[0].event_type == EVENT_COMMAND_RECEIVED
assert events[0].data == {
'request_id': REQ_ID,
'entity_id': 'light.non_existing',
'execution': {
'command': 'action.devices.commands.OnOff',
'params': {
'on': True
}
}
}
assert events[1].event_type == EVENT_COMMAND_RECEIVED
assert events[1].data == {
'request_id': REQ_ID,
'entity_id': 'light.non_existing',
'execution': {
'command': 'action.devices.commands.BrightnessAbsolute',
'params': {
'brightness': 20
}
}
}
assert events[2].event_type == EVENT_COMMAND_RECEIVED
assert events[2].data == {
'request_id': REQ_ID,
'entity_id': 'light.ceiling_lights',
'execution': {
'command': 'action.devices.commands.OnOff',
'params': {
'on': True
}
}
}
assert events[3].event_type == EVENT_COMMAND_RECEIVED
assert events[3].data == {
'request_id': REQ_ID,
'entity_id': 'light.ceiling_lights',
'execution': {
'command': 'action.devices.commands.BrightnessAbsolute',
'params': {
'brightness': 20
}
}
}
async def test_raising_error_trait(hass):
"""Test raising an error while executing a trait command."""
@ -218,6 +299,11 @@ async def test_raising_error_trait(hass):
ATTR_SUPPORTED_FEATURES: SUPPORT_OPERATION_MODE,
ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS,
})
events = []
hass.bus.async_listen(EVENT_COMMAND_RECEIVED, events.append)
await hass.async_block_till_done()
result = await sh.async_handle_message(hass, BASIC_CONFIG, {
"requestId": REQ_ID,
"inputs": [{
@ -250,6 +336,19 @@ async def test_raising_error_trait(hass):
}
}
assert len(events) == 1
assert events[0].event_type == EVENT_COMMAND_RECEIVED
assert events[0].data == {
'request_id': REQ_ID,
'entity_id': 'climate.bla',
'execution': {
'command': 'action.devices.commands.ThermostatTemperatureSetpoint',
'params': {
'thermostatTemperatureSetpoint': 10
}
}
}
def test_serialize_input_boolean():
"""Test serializing an input boolean entity."""