core/homeassistant/components/google_assistant/smart_home.py

358 lines
10 KiB
Python
Raw Normal View History

"""Support for Google Assistant Smart Home API."""
from collections.abc import Mapping
from itertools import product
import logging
from homeassistant.util.decorator import Registry
from homeassistant.core import callback
from homeassistant.const import (
Add support for locks in google assistant component (#18233) * Add support for locks in google assistant component This is supported by the smarthome API, but there is no documentation for it. This work is based on an article I found with screenshots of documentation that was erroneously uploaded: https://www.androidpolice.com/2018/01/17/google-assistant-home-can-now-natively-control-smart-locks-august-vivint-first-supported/ Google Assistant now supports unlocking certain locks - Nest and August come to mind - via this API, and this commit allows Home Assistant to do so as well. Notably, I've added a config option `allow_unlock` that controls whether we actually honor requests to unlock a lock via the google assistant. It defaults to false. Additionally, we add the functionNotSupported error, which makes a little more sense when we're unable to execute the desired state transition. https://developers.google.com/actions/reference/smarthome/errors-exceptions#exception_list * Fix linter warnings * Ensure that certain groups are never exposed to cloud entities For example, the group.all_locks entity - we should probably never expose this to third party cloud integrations. It's risky. This is not configurable, but can be extended by adding to the cloud.const.NEVER_EXPOSED_ENTITIES array. It's implemented in a modestly hacky fashion, because we determine whether or not a entity should be excluded/included in several ways. Notably, we define this array in the top level const.py, to avoid circular import problems between the cloud/alexa components.
2018-11-06 09:39:10 +00:00
CLOUD_NEVER_EXPOSED_ENTITIES, CONF_NAME, STATE_UNAVAILABLE,
ATTR_SUPPORTED_FEATURES
)
from homeassistant.components import (
climate,
cover,
fan,
group,
input_boolean,
light,
Add support for locks in google assistant component (#18233) * Add support for locks in google assistant component This is supported by the smarthome API, but there is no documentation for it. This work is based on an article I found with screenshots of documentation that was erroneously uploaded: https://www.androidpolice.com/2018/01/17/google-assistant-home-can-now-natively-control-smart-locks-august-vivint-first-supported/ Google Assistant now supports unlocking certain locks - Nest and August come to mind - via this API, and this commit allows Home Assistant to do so as well. Notably, I've added a config option `allow_unlock` that controls whether we actually honor requests to unlock a lock via the google assistant. It defaults to false. Additionally, we add the functionNotSupported error, which makes a little more sense when we're unable to execute the desired state transition. https://developers.google.com/actions/reference/smarthome/errors-exceptions#exception_list * Fix linter warnings * Ensure that certain groups are never exposed to cloud entities For example, the group.all_locks entity - we should probably never expose this to third party cloud integrations. It's risky. This is not configurable, but can be extended by adding to the cloud.const.NEVER_EXPOSED_ENTITIES array. It's implemented in a modestly hacky fashion, because we determine whether or not a entity should be excluded/included in several ways. Notably, we define this array in the top level const.py, to avoid circular import problems between the cloud/alexa components.
2018-11-06 09:39:10 +00:00
lock,
media_player,
scene,
script,
switch,
vacuum,
)
Add support for locks in google assistant component (#18233) * Add support for locks in google assistant component This is supported by the smarthome API, but there is no documentation for it. This work is based on an article I found with screenshots of documentation that was erroneously uploaded: https://www.androidpolice.com/2018/01/17/google-assistant-home-can-now-natively-control-smart-locks-august-vivint-first-supported/ Google Assistant now supports unlocking certain locks - Nest and August come to mind - via this API, and this commit allows Home Assistant to do so as well. Notably, I've added a config option `allow_unlock` that controls whether we actually honor requests to unlock a lock via the google assistant. It defaults to false. Additionally, we add the functionNotSupported error, which makes a little more sense when we're unable to execute the desired state transition. https://developers.google.com/actions/reference/smarthome/errors-exceptions#exception_list * Fix linter warnings * Ensure that certain groups are never exposed to cloud entities For example, the group.all_locks entity - we should probably never expose this to third party cloud integrations. It's risky. This is not configurable, but can be extended by adding to the cloud.const.NEVER_EXPOSED_ENTITIES array. It's implemented in a modestly hacky fashion, because we determine whether or not a entity should be excluded/included in several ways. Notably, we define this array in the top level const.py, to avoid circular import problems between the cloud/alexa components.
2018-11-06 09:39:10 +00:00
from . import trait
from .const import (
Add support for locks in google assistant component (#18233) * Add support for locks in google assistant component This is supported by the smarthome API, but there is no documentation for it. This work is based on an article I found with screenshots of documentation that was erroneously uploaded: https://www.androidpolice.com/2018/01/17/google-assistant-home-can-now-natively-control-smart-locks-august-vivint-first-supported/ Google Assistant now supports unlocking certain locks - Nest and August come to mind - via this API, and this commit allows Home Assistant to do so as well. Notably, I've added a config option `allow_unlock` that controls whether we actually honor requests to unlock a lock via the google assistant. It defaults to false. Additionally, we add the functionNotSupported error, which makes a little more sense when we're unable to execute the desired state transition. https://developers.google.com/actions/reference/smarthome/errors-exceptions#exception_list * Fix linter warnings * Ensure that certain groups are never exposed to cloud entities For example, the group.all_locks entity - we should probably never expose this to third party cloud integrations. It's risky. This is not configurable, but can be extended by adding to the cloud.const.NEVER_EXPOSED_ENTITIES array. It's implemented in a modestly hacky fashion, because we determine whether or not a entity should be excluded/included in several ways. Notably, we define this array in the top level const.py, to avoid circular import problems between the cloud/alexa components.
2018-11-06 09:39:10 +00:00
TYPE_LIGHT, TYPE_LOCK, TYPE_SCENE, TYPE_SWITCH, TYPE_VACUUM,
TYPE_THERMOSTAT, TYPE_FAN,
CONF_ALIASES, CONF_ROOM_HINT,
Add support for locks in google assistant component (#18233) * Add support for locks in google assistant component This is supported by the smarthome API, but there is no documentation for it. This work is based on an article I found with screenshots of documentation that was erroneously uploaded: https://www.androidpolice.com/2018/01/17/google-assistant-home-can-now-natively-control-smart-locks-august-vivint-first-supported/ Google Assistant now supports unlocking certain locks - Nest and August come to mind - via this API, and this commit allows Home Assistant to do so as well. Notably, I've added a config option `allow_unlock` that controls whether we actually honor requests to unlock a lock via the google assistant. It defaults to false. Additionally, we add the functionNotSupported error, which makes a little more sense when we're unable to execute the desired state transition. https://developers.google.com/actions/reference/smarthome/errors-exceptions#exception_list * Fix linter warnings * Ensure that certain groups are never exposed to cloud entities For example, the group.all_locks entity - we should probably never expose this to third party cloud integrations. It's risky. This is not configurable, but can be extended by adding to the cloud.const.NEVER_EXPOSED_ENTITIES array. It's implemented in a modestly hacky fashion, because we determine whether or not a entity should be excluded/included in several ways. Notably, we define this array in the top level const.py, to avoid circular import problems between the cloud/alexa components.
2018-11-06 09:39:10 +00:00
ERR_FUNCTION_NOT_SUPPORTED, ERR_PROTOCOL_ERROR, ERR_DEVICE_OFFLINE,
ERR_UNKNOWN_ERROR
)
from .helpers import SmartHomeError
HANDLERS = Registry()
_LOGGER = logging.getLogger(__name__)
DOMAIN_TO_GOOGLE_TYPES = {
climate.DOMAIN: TYPE_THERMOSTAT,
cover.DOMAIN: TYPE_SWITCH,
fan.DOMAIN: TYPE_FAN,
group.DOMAIN: TYPE_SWITCH,
input_boolean.DOMAIN: TYPE_SWITCH,
light.DOMAIN: TYPE_LIGHT,
Add support for locks in google assistant component (#18233) * Add support for locks in google assistant component This is supported by the smarthome API, but there is no documentation for it. This work is based on an article I found with screenshots of documentation that was erroneously uploaded: https://www.androidpolice.com/2018/01/17/google-assistant-home-can-now-natively-control-smart-locks-august-vivint-first-supported/ Google Assistant now supports unlocking certain locks - Nest and August come to mind - via this API, and this commit allows Home Assistant to do so as well. Notably, I've added a config option `allow_unlock` that controls whether we actually honor requests to unlock a lock via the google assistant. It defaults to false. Additionally, we add the functionNotSupported error, which makes a little more sense when we're unable to execute the desired state transition. https://developers.google.com/actions/reference/smarthome/errors-exceptions#exception_list * Fix linter warnings * Ensure that certain groups are never exposed to cloud entities For example, the group.all_locks entity - we should probably never expose this to third party cloud integrations. It's risky. This is not configurable, but can be extended by adding to the cloud.const.NEVER_EXPOSED_ENTITIES array. It's implemented in a modestly hacky fashion, because we determine whether or not a entity should be excluded/included in several ways. Notably, we define this array in the top level const.py, to avoid circular import problems between the cloud/alexa components.
2018-11-06 09:39:10 +00:00
lock.DOMAIN: TYPE_LOCK,
media_player.DOMAIN: TYPE_SWITCH,
scene.DOMAIN: TYPE_SCENE,
script.DOMAIN: TYPE_SCENE,
switch.DOMAIN: TYPE_SWITCH,
vacuum.DOMAIN: TYPE_VACUUM,
}
def deep_update(target, source):
"""Update a nested dictionary with another nested dictionary."""
for key, value in source.items():
if isinstance(value, Mapping):
target[key] = deep_update(target.get(key, {}), value)
else:
target[key] = value
return target
class _GoogleEntity:
"""Adaptation of Entity expressed in Google's terms."""
def __init__(self, hass, config, state):
self.hass = hass
self.config = config
self.state = state
@property
def entity_id(self):
"""Return entity ID."""
return self.state.entity_id
@callback
def traits(self):
"""Return traits for entity."""
state = self.state
domain = state.domain
features = state.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
Add support for locks in google assistant component (#18233) * Add support for locks in google assistant component This is supported by the smarthome API, but there is no documentation for it. This work is based on an article I found with screenshots of documentation that was erroneously uploaded: https://www.androidpolice.com/2018/01/17/google-assistant-home-can-now-natively-control-smart-locks-august-vivint-first-supported/ Google Assistant now supports unlocking certain locks - Nest and August come to mind - via this API, and this commit allows Home Assistant to do so as well. Notably, I've added a config option `allow_unlock` that controls whether we actually honor requests to unlock a lock via the google assistant. It defaults to false. Additionally, we add the functionNotSupported error, which makes a little more sense when we're unable to execute the desired state transition. https://developers.google.com/actions/reference/smarthome/errors-exceptions#exception_list * Fix linter warnings * Ensure that certain groups are never exposed to cloud entities For example, the group.all_locks entity - we should probably never expose this to third party cloud integrations. It's risky. This is not configurable, but can be extended by adding to the cloud.const.NEVER_EXPOSED_ENTITIES array. It's implemented in a modestly hacky fashion, because we determine whether or not a entity should be excluded/included in several ways. Notably, we define this array in the top level const.py, to avoid circular import problems between the cloud/alexa components.
2018-11-06 09:39:10 +00:00
return [Trait(self.hass, state, self.config) for Trait in trait.TRAITS
if Trait.supported(domain, features)]
@callback
def sync_serialize(self):
"""Serialize entity for a SYNC response.
https://developers.google.com/actions/smarthome/create-app#actiondevicessync
"""
state = self.state
# When a state is unavailable, the attributes that describe
# capabilities will be stripped. For example, a light entity will miss
# the min/max mireds. Therefore they will be excluded from a sync.
if state.state == STATE_UNAVAILABLE:
return None
entity_config = self.config.entity_config.get(state.entity_id, {})
name = (entity_config.get(CONF_NAME) or state.name).strip()
# If an empty string
if not name:
return None
traits = self.traits()
# Found no supported traits for this entity
if not traits:
return None
device = {
'id': state.entity_id,
'name': {
'name': name
},
'attributes': {},
'traits': [trait.name for trait in traits],
'willReportState': False,
'type': DOMAIN_TO_GOOGLE_TYPES[state.domain],
}
# use aliases
aliases = entity_config.get(CONF_ALIASES)
if aliases:
device['name']['nicknames'] = aliases
# add room hint if annotated
room = entity_config.get(CONF_ROOM_HINT)
if room:
device['roomHint'] = room
for trt in traits:
device['attributes'].update(trt.sync_attributes())
return device
@callback
def query_serialize(self):
"""Serialize entity for a QUERY response.
https://developers.google.com/actions/smarthome/create-app#actiondevicesquery
"""
state = self.state
if state.state == STATE_UNAVAILABLE:
return {'online': False}
attrs = {'online': True}
for trt in self.traits():
deep_update(attrs, trt.query_attributes())
return attrs
async def execute(self, command, 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)
executed = True
break
if not executed:
raise SmartHomeError(
Add support for locks in google assistant component (#18233) * Add support for locks in google assistant component This is supported by the smarthome API, but there is no documentation for it. This work is based on an article I found with screenshots of documentation that was erroneously uploaded: https://www.androidpolice.com/2018/01/17/google-assistant-home-can-now-natively-control-smart-locks-august-vivint-first-supported/ Google Assistant now supports unlocking certain locks - Nest and August come to mind - via this API, and this commit allows Home Assistant to do so as well. Notably, I've added a config option `allow_unlock` that controls whether we actually honor requests to unlock a lock via the google assistant. It defaults to false. Additionally, we add the functionNotSupported error, which makes a little more sense when we're unable to execute the desired state transition. https://developers.google.com/actions/reference/smarthome/errors-exceptions#exception_list * Fix linter warnings * Ensure that certain groups are never exposed to cloud entities For example, the group.all_locks entity - we should probably never expose this to third party cloud integrations. It's risky. This is not configurable, but can be extended by adding to the cloud.const.NEVER_EXPOSED_ENTITIES array. It's implemented in a modestly hacky fashion, because we determine whether or not a entity should be excluded/included in several ways. Notably, we define this array in the top level const.py, to avoid circular import problems between the cloud/alexa components.
2018-11-06 09:39:10 +00:00
ERR_FUNCTION_NOT_SUPPORTED,
'Unable to execute {} for {}'.format(command,
self.state.entity_id))
@callback
def async_update(self):
"""Update the entity with latest info from Home Assistant."""
self.state = self.hass.states.get(self.entity_id)
async def async_handle_message(hass, config, message):
"""Handle incoming API messages."""
response = await _process(hass, config, message)
if response and 'errorCode' in response['payload']:
_LOGGER.error('Error handling message %s: %s',
message, response['payload'])
return response
async def _process(hass, config, 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,
'payload': {'errorCode': ERR_PROTOCOL_ERROR}
}
handler = HANDLERS.get(inputs[0].get('intent'))
if handler is None:
return {
'requestId': request_id,
'payload': {'errorCode': ERR_PROTOCOL_ERROR}
}
try:
result = await handler(hass, config, inputs[0].get('payload'))
except SmartHomeError as err:
return {
'requestId': request_id,
'payload': {'errorCode': err.code}
}
except Exception: # pylint: disable=broad-except
_LOGGER.exception('Unexpected error')
return {
'requestId': request_id,
'payload': {'errorCode': ERR_UNKNOWN_ERROR}
}
if result is None:
return None
return {'requestId': request_id, 'payload': result}
@HANDLERS.register('action.devices.SYNC')
async def async_devices_sync(hass, config, payload):
"""Handle action.devices.SYNC request.
https://developers.google.com/actions/smarthome/create-app#actiondevicessync
"""
devices = []
for state in hass.states.async_all():
Add support for locks in google assistant component (#18233) * Add support for locks in google assistant component This is supported by the smarthome API, but there is no documentation for it. This work is based on an article I found with screenshots of documentation that was erroneously uploaded: https://www.androidpolice.com/2018/01/17/google-assistant-home-can-now-natively-control-smart-locks-august-vivint-first-supported/ Google Assistant now supports unlocking certain locks - Nest and August come to mind - via this API, and this commit allows Home Assistant to do so as well. Notably, I've added a config option `allow_unlock` that controls whether we actually honor requests to unlock a lock via the google assistant. It defaults to false. Additionally, we add the functionNotSupported error, which makes a little more sense when we're unable to execute the desired state transition. https://developers.google.com/actions/reference/smarthome/errors-exceptions#exception_list * Fix linter warnings * Ensure that certain groups are never exposed to cloud entities For example, the group.all_locks entity - we should probably never expose this to third party cloud integrations. It's risky. This is not configurable, but can be extended by adding to the cloud.const.NEVER_EXPOSED_ENTITIES array. It's implemented in a modestly hacky fashion, because we determine whether or not a entity should be excluded/included in several ways. Notably, we define this array in the top level const.py, to avoid circular import problems between the cloud/alexa components.
2018-11-06 09:39:10 +00:00
if state.entity_id in CLOUD_NEVER_EXPOSED_ENTITIES:
continue
if not config.should_expose(state):
continue
entity = _GoogleEntity(hass, config, state)
serialized = entity.sync_serialize()
if serialized is None:
_LOGGER.debug("No mapping for %s domain", entity.state)
continue
devices.append(serialized)
return {
'agentUserId': config.agent_user_id,
'devices': devices,
}
@HANDLERS.register('action.devices.QUERY')
async def async_devices_query(hass, config, payload):
"""Handle action.devices.QUERY request.
https://developers.google.com/actions/smarthome/create-app#actiondevicesquery
"""
devices = {}
for device in payload.get('devices', []):
devid = device['id']
state = hass.states.get(devid)
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()
return {'devices': devices}
@HANDLERS.register('action.devices.EXECUTE')
async def handle_devices_execute(hass, config, payload):
"""Handle action.devices.EXECUTE request.
https://developers.google.com/actions/smarthome/create-app#actiondevicesexecute
"""
entities = {}
results = {}
for command in payload['commands']:
for device, execution in product(command['devices'],
command['execution']):
entity_id = device['id']
# Happens if error occurred. Skip entity for further processing
if entity_id in results:
continue
if entity_id not in entities:
state = hass.states.get(entity_id)
if state is None:
results[entity_id] = {
'ids': [entity_id],
'status': 'ERROR',
'errorCode': ERR_DEVICE_OFFLINE
}
continue
entities[entity_id] = _GoogleEntity(hass, config, state)
try:
await entities[entity_id].execute(execution['command'],
execution.get('params', {}))
except SmartHomeError as err:
results[entity_id] = {
'ids': [entity_id],
'status': 'ERROR',
'errorCode': err.code
}
final_results = list(results.values())
for entity in entities.values():
if entity.entity_id in results:
continue
entity.async_update()
final_results.append({
'ids': [entity.entity_id],
'status': 'SUCCESS',
'states': entity.query_serialize(),
})
return {'commands': final_results}
@HANDLERS.register('action.devices.DISCONNECT')
async def async_devices_disconnect(hass, config, payload):
"""Handle action.devices.DISCONNECT request.
https://developers.google.com/actions/smarthome/create#actiondevicesdisconnect
"""
return None
def turned_off_response(message):
"""Return a device turned off response."""
return {
'requestId': message.get('requestId'),
'payload': {'errorCode': 'deviceTurnedOff'}
}