Skip to content

Commit

Permalink
Google Assistant: Create and pass context to service calls (home-assi…
Browse files Browse the repository at this point in the history
…stant#21551)

* Google Assistant: Create and pass context to service calls

* Refactor request data into separate object and pass to execute.
  • Loading branch information
Swamp-Ig authored and balloob committed Mar 6, 2019
1 parent fc1ee9b commit d1038ea
Show file tree
Hide file tree
Showing 8 changed files with 345 additions and 265 deletions.
1 change: 0 additions & 1 deletion homeassistant/components/cloud/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,6 @@ def should_expose(entity):
self._gactions_config = ga_h.Config(
should_expose=should_expose,
allow_unlock=self.prefs.google_allow_unlock,
agent_user_id=self.claims['cognito:username'],
entity_config=conf.get(CONF_ENTITY_CONFIG),
)

Expand Down
4 changes: 3 additions & 1 deletion homeassistant/components/cloud/iot.py
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,9 @@ def async_handle_google_actions(hass, cloud, payload):
return ga.turned_off_response(payload)

result = yield from ga.async_handle_message(
hass, cloud.gactions_config, payload)
hass, cloud.gactions_config,
cloud.claims['cognito:username'],
payload)
return result


Expand Down
14 changes: 12 additions & 2 deletions homeassistant/components/google_assistant/helpers.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Helper classes for Google Assistant integration."""
from homeassistant.core import Context


class SmartHomeError(Exception):
Expand All @@ -16,10 +17,19 @@ def __init__(self, code, msg):
class Config:
"""Hold the configuration for Google Assistant."""

def __init__(self, should_expose, allow_unlock, agent_user_id,
def __init__(self, should_expose, allow_unlock,
entity_config=None):
"""Initialize the configuration."""
self.should_expose = should_expose
self.agent_user_id = agent_user_id
self.entity_config = entity_config or {}
self.allow_unlock = allow_unlock


class RequestData:
"""Hold data associated with a particular request."""

def __init__(self, config, user_id, request_id):
"""Initialize the request data."""
self.config = config
self.request_id = request_id
self.context = Context(user_id=user_id)
15 changes: 7 additions & 8 deletions homeassistant/components/google_assistant/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,17 +71,16 @@ class GoogleAssistantView(HomeAssistantView):

def __init__(self, is_exposed, entity_config, allow_unlock):
"""Initialize the Google Assistant request handler."""
self.is_exposed = is_exposed
self.entity_config = entity_config
self.allow_unlock = allow_unlock
self.config = Config(is_exposed,
allow_unlock,
entity_config)

async def post(self, request: Request) -> Response:
"""Handle Google Assistant requests."""
message = await request.json() # type: dict
config = Config(self.is_exposed,
self.allow_unlock,
request['hass_user'].id,
self.entity_config)
result = await async_handle_message(
request.app['hass'], config, message)
request.app['hass'],
self.config,
request['hass_user'].id,
message)
return self.json(result)
84 changes: 48 additions & 36 deletions homeassistant/components/google_assistant/smart_home.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
ERR_UNKNOWN_ERROR,
EVENT_COMMAND_RECEIVED, EVENT_SYNC_RECEIVED, EVENT_QUERY_RECEIVED
)
from .helpers import SmartHomeError
from .helpers import SmartHomeError, RequestData

HANDLERS = Registry()
_LOGGER = logging.getLogger(__name__)
Expand Down Expand Up @@ -87,7 +87,8 @@ def traits(self):
domain = state.domain
features = state.attributes.get(ATTR_SUPPORTED_FEATURES, 0)

return [Trait(self.hass, state, self.config) for Trait in trait.TRAITS
return [Trait(self.hass, state, self.config)
for Trait in trait.TRAITS
if Trait.supported(domain, features)]

async def sync_serialize(self):
Expand Down Expand Up @@ -178,15 +179,15 @@ def query_serialize(self):

return attrs

async def execute(self, command, params):
async def execute(self, command, data, params):
"""Execute a command.
https://developers.google.com/actions/smarthome/create-app#actiondevicesexecute
"""
executed = False
for trt in self.traits():
if trt.can_execute(command, params):
await trt.execute(command, params)
await trt.execute(command, data, params)
executed = True
break

Expand All @@ -202,9 +203,13 @@ def async_update(self):
self.state = self.hass.states.get(self.entity_id)


async def async_handle_message(hass, config, message):
async def async_handle_message(hass, config, user_id, message):
"""Handle incoming API messages."""
response = await _process(hass, config, message)
request_id = message.get('requestId') # type: str

data = RequestData(config, user_id, request_id)

response = await _process(hass, data, message)

if response and 'errorCode' in response['payload']:
_LOGGER.error('Error handling message %s: %s',
Expand All @@ -213,64 +218,63 @@ async def async_handle_message(hass, config, message):
return response


async def _process(hass, config, message):
async def _process(hass, data, message):
"""Process a message."""
request_id = message.get('requestId') # type: str
inputs = message.get('inputs') # type: list

if len(inputs) != 1:
return {
'requestId': request_id,
'requestId': data.request_id,
'payload': {'errorCode': ERR_PROTOCOL_ERROR}
}

handler = HANDLERS.get(inputs[0].get('intent'))

if handler is None:
return {
'requestId': request_id,
'requestId': data.request_id,
'payload': {'errorCode': ERR_PROTOCOL_ERROR}
}

try:
result = await handler(hass, config, request_id,
inputs[0].get('payload'))
result = await handler(hass, data, inputs[0].get('payload'))
except SmartHomeError as err:
return {
'requestId': request_id,
'requestId': data.request_id,
'payload': {'errorCode': err.code}
}
except Exception: # pylint: disable=broad-except
_LOGGER.exception('Unexpected error')
return {
'requestId': request_id,
'requestId': data.request_id,
'payload': {'errorCode': ERR_UNKNOWN_ERROR}
}

if result is None:
return None
return {'requestId': request_id, 'payload': result}
return {'requestId': data.request_id, 'payload': result}


@HANDLERS.register('action.devices.SYNC')
async def async_devices_sync(hass, config, request_id, payload):
async def async_devices_sync(hass, data, 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
})
hass.bus.async_fire(
EVENT_SYNC_RECEIVED,
{'request_id': data.request_id},
context=data.context)

devices = []
for state in hass.states.async_all():
if state.entity_id in CLOUD_NEVER_EXPOSED_ENTITIES:
continue

if not config.should_expose(state):
if not data.config.should_expose(state):
continue

entity = _GoogleEntity(hass, config, state)
entity = _GoogleEntity(hass, data.config, state)
serialized = await entity.sync_serialize()

if serialized is None:
Expand All @@ -280,15 +284,15 @@ async def async_devices_sync(hass, config, request_id, payload):
devices.append(serialized)

response = {
'agentUserId': config.agent_user_id,
'agentUserId': data.context.user_id,
'devices': devices,
}

return response


@HANDLERS.register('action.devices.QUERY')
async def async_devices_query(hass, config, request_id, payload):
async def async_devices_query(hass, data, payload):
"""Handle action.devices.QUERY request.
https://developers.google.com/actions/smarthome/create-app#actiondevicesquery
Expand All @@ -298,23 +302,27 @@ async def async_devices_query(hass, config, request_id, payload):
devid = device['id']
state = hass.states.get(devid)

hass.bus.async_fire(EVENT_QUERY_RECEIVED, {
'request_id': request_id,
ATTR_ENTITY_ID: devid,
})
hass.bus.async_fire(
EVENT_QUERY_RECEIVED,
{
'request_id': data.request_id,
ATTR_ENTITY_ID: devid,
},
context=data.context)

if not state:
# If we can't find a state, the device is offline
devices[devid] = {'online': False}
continue

devices[devid] = _GoogleEntity(hass, config, state).query_serialize()
entity = _GoogleEntity(hass, data.config, state)
devices[devid] = entity.query_serialize()

return {'devices': devices}


@HANDLERS.register('action.devices.EXECUTE')
async def handle_devices_execute(hass, config, request_id, payload):
async def handle_devices_execute(hass, data, payload):
"""Handle action.devices.EXECUTE request.
https://developers.google.com/actions/smarthome/create-app#actiondevicesexecute
Expand All @@ -327,11 +335,14 @@ async def handle_devices_execute(hass, config, request_id, 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
})
hass.bus.async_fire(
EVENT_COMMAND_RECEIVED,
{
'request_id': data.request_id,
ATTR_ENTITY_ID: entity_id,
'execution': execution
},
context=data.context)

# Happens if error occurred. Skip entity for further processing
if entity_id in results:
Expand All @@ -348,10 +359,11 @@ async def handle_devices_execute(hass, config, request_id, payload):
}
continue

entities[entity_id] = _GoogleEntity(hass, config, state)
entities[entity_id] = _GoogleEntity(hass, data.config, state)

try:
await entities[entity_id].execute(execution['command'],
data,
execution.get('params', {}))
except SmartHomeError as err:
results[entity_id] = {
Expand All @@ -378,7 +390,7 @@ async def handle_devices_execute(hass, config, request_id, payload):


@HANDLERS.register('action.devices.DISCONNECT')
async def async_devices_disconnect(hass, config, request_id, payload):
async def async_devices_disconnect(hass, data, payload):
"""Handle action.devices.DISCONNECT request.
https://developers.google.com/actions/smarthome/create#actiondevicesdisconnect
Expand Down
Loading

0 comments on commit d1038ea

Please sign in to comment.