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, CONF_EXPOSE, CONF_ALIASES, CONF_ROOM_HINT, CONF_ALLOW_UNLOCK,
DEFAULT_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 from .http import async_register_http
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)

View file

@ -42,3 +42,8 @@ ERR_NOT_SUPPORTED = "notSupported"
ERR_PROTOCOL_ERROR = 'protocolError' ERR_PROTOCOL_ERROR = 'protocolError'
ERR_UNKNOWN_ERROR = 'unknownError' ERR_UNKNOWN_ERROR = 'unknownError'
ERR_FUNCTION_NOT_SUPPORTED = 'functionNotSupported' 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.core import callback
from homeassistant.const import ( from homeassistant.const import (
CLOUD_NEVER_EXPOSED_ENTITIES, CONF_NAME, STATE_UNAVAILABLE, CLOUD_NEVER_EXPOSED_ENTITIES, CONF_NAME, STATE_UNAVAILABLE,
ATTR_SUPPORTED_FEATURES ATTR_SUPPORTED_FEATURES, ATTR_ENTITY_ID,
) )
from homeassistant.components import ( from homeassistant.components import (
climate, climate,
@ -32,7 +32,8 @@ from .const import (
TYPE_THERMOSTAT, TYPE_FAN, TYPE_THERMOSTAT, TYPE_FAN,
CONF_ALIASES, CONF_ROOM_HINT, CONF_ALIASES, CONF_ROOM_HINT,
ERR_FUNCTION_NOT_SUPPORTED, ERR_PROTOCOL_ERROR, ERR_DEVICE_OFFLINE, 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 from .helpers import SmartHomeError
@ -214,7 +215,8 @@ async def _process(hass, config, message):
} }
try: 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: except SmartHomeError as err:
return { return {
'requestId': request_id, 'requestId': request_id,
@ -233,11 +235,15 @@ async def _process(hass, config, message):
@HANDLERS.register('action.devices.SYNC') @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. """Handle action.devices.SYNC request.
https://developers.google.com/actions/smarthome/create-app#actiondevicessync https://developers.google.com/actions/smarthome/create-app#actiondevicessync
""" """
hass.bus.async_fire(EVENT_SYNC_RECEIVED, {
'request_id': request_id
})
devices = [] devices = []
for state in hass.states.async_all(): for state in hass.states.async_all():
if state.entity_id in CLOUD_NEVER_EXPOSED_ENTITIES: if state.entity_id in CLOUD_NEVER_EXPOSED_ENTITIES:
@ -255,14 +261,16 @@ async def async_devices_sync(hass, config, payload):
devices.append(serialized) devices.append(serialized)
return { response = {
'agentUserId': config.agent_user_id, 'agentUserId': config.agent_user_id,
'devices': devices, 'devices': devices,
} }
return response
@HANDLERS.register('action.devices.QUERY') @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. """Handle action.devices.QUERY request.
https://developers.google.com/actions/smarthome/create-app#actiondevicesquery https://developers.google.com/actions/smarthome/create-app#actiondevicesquery
@ -272,6 +280,11 @@ async def async_devices_query(hass, config, payload):
devid = device['id'] devid = device['id']
state = hass.states.get(devid) state = hass.states.get(devid)
hass.bus.async_fire(EVENT_QUERY_RECEIVED, {
'request_id': request_id,
ATTR_ENTITY_ID: devid,
})
if not state: if not state:
# If we can't find a state, the device is offline # If we can't find a state, the device is offline
devices[devid] = {'online': False} devices[devid] = {'online': False}
@ -283,7 +296,7 @@ async def async_devices_query(hass, config, payload):
@HANDLERS.register('action.devices.EXECUTE') @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. """Handle action.devices.EXECUTE request.
https://developers.google.com/actions/smarthome/create-app#actiondevicesexecute https://developers.google.com/actions/smarthome/create-app#actiondevicesexecute
@ -296,6 +309,12 @@ async def handle_devices_execute(hass, config, payload):
command['execution']): command['execution']):
entity_id = device['id'] 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 # Happens if error occurred. Skip entity for further processing
if entity_id in results: if entity_id in results:
continue continue
@ -341,7 +360,7 @@ async def handle_devices_execute(hass, config, payload):
@HANDLERS.register('action.devices.DISCONNECT') @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. """Handle action.devices.DISCONNECT request.
https://developers.google.com/actions/smarthome/create#actiondevicesdisconnect 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 ATTR_MIN_TEMP, ATTR_MAX_TEMP, STATE_HEAT, SUPPORT_OPERATION_MODE
) )
from homeassistant.components.google_assistant import ( 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 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, { result = await sh.async_handle_message(hass, config, {
"requestId": REQ_ID, "requestId": REQ_ID,
"inputs": [{ "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): async def test_query_message(hass):
@ -109,6 +120,9 @@ async def test_query_message(hass):
light2.entity_id = 'light.another_light' light2.entity_id = 'light.another_light'
await light2.async_update_ha_state() 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, { result = await sh.async_handle_message(hass, BASIC_CONFIG, {
"requestId": REQ_ID, "requestId": REQ_ID,
"inputs": [{ "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): async def test_execute(hass):
"""Test an execute command.""" """Test an execute command."""
await async_setup_component(hass, 'light', { await async_setup_component(hass, 'light', {
'light': {'platform': 'demo'} 'light': {'platform': 'demo'}
}) })
events = []
hass.bus.async_listen(EVENT_COMMAND_RECEIVED, events.append)
await hass.services.async_call( await hass.services.async_call(
'light', 'turn_off', {'entity_id': 'light.ceiling_lights'}, 'light', 'turn_off', {'entity_id': 'light.ceiling_lights'},
blocking=True) 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): async def test_raising_error_trait(hass):
"""Test raising an error while executing a trait command.""" """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_SUPPORTED_FEATURES: SUPPORT_OPERATION_MODE,
ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS, 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, { result = await sh.async_handle_message(hass, BASIC_CONFIG, {
"requestId": REQ_ID, "requestId": REQ_ID,
"inputs": [{ "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(): def test_serialize_input_boolean():
"""Test serializing an input boolean entity.""" """Test serializing an input boolean entity."""