Move Google Assistant entity config out of customize (#11499)

* Move Google Assistant entity config out of customize

* CONF_ALIAS -> CONF_ALIASES

* Lint
pull/11557/head
Paulus Schoutsen 2018-01-09 15:14:56 -08:00 committed by GitHub
parent 13042d5557
commit 8313225b40
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 86 additions and 60 deletions

View File

@ -10,7 +10,7 @@ import async_timeout
import voluptuous as vol
from homeassistant.const import (
EVENT_HOMEASSISTANT_START, CONF_REGION, CONF_MODE)
EVENT_HOMEASSISTANT_START, CONF_REGION, CONF_MODE, CONF_NAME, CONF_TYPE)
from homeassistant.helpers import entityfilter
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession
@ -31,6 +31,7 @@ CONF_FILTER = 'filter'
CONF_COGNITO_CLIENT_ID = 'cognito_client_id'
CONF_RELAYER = 'relayer'
CONF_USER_POOL_ID = 'user_pool_id'
CONF_ALIASES = 'aliases'
MODE_DEV = 'development'
DEFAULT_MODE = 'production'
@ -44,6 +45,12 @@ ALEXA_ENTITY_SCHEMA = vol.Schema({
vol.Optional(alexa_sh.CONF_NAME): cv.string,
})
GOOGLE_ENTITY_SCHEMA = vol.Schema({
vol.Optional(CONF_NAME): cv.string,
vol.Optional(CONF_TYPE): vol.In(ga_sh.MAPPING_COMPONENT),
vol.Optional(CONF_ALIASES): vol.All(cv.ensure_list, [cv.string])
})
ASSISTANT_SCHEMA = vol.Schema({
vol.Optional(
CONF_FILTER,
@ -55,6 +62,10 @@ ALEXA_SCHEMA = ASSISTANT_SCHEMA.extend({
vol.Optional(CONF_ENTITY_CONFIG): {cv.entity_id: ALEXA_ENTITY_SCHEMA}
})
GACTIONS_SCHEMA = ASSISTANT_SCHEMA.extend({
vol.Optional(CONF_ENTITY_CONFIG): {cv.entity_id: GOOGLE_ENTITY_SCHEMA}
})
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Optional(CONF_MODE, default=DEFAULT_MODE):
@ -65,7 +76,7 @@ CONFIG_SCHEMA = vol.Schema({
vol.Optional(CONF_REGION): str,
vol.Optional(CONF_RELAYER): str,
vol.Optional(CONF_ALEXA): ALEXA_SCHEMA,
vol.Optional(CONF_GOOGLE_ACTIONS): ASSISTANT_SCHEMA,
vol.Optional(CONF_GOOGLE_ACTIONS): GACTIONS_SCHEMA,
}),
}, extra=vol.ALLOW_EXTRA)
@ -79,14 +90,15 @@ def async_setup(hass, config):
kwargs = {CONF_MODE: DEFAULT_MODE}
alexa_conf = kwargs.pop(CONF_ALEXA, None) or ALEXA_SCHEMA({})
gactions_conf = (kwargs.pop(CONF_GOOGLE_ACTIONS, None) or
ASSISTANT_SCHEMA({}))
if CONF_GOOGLE_ACTIONS not in kwargs:
kwargs[CONF_GOOGLE_ACTIONS] = GACTIONS_SCHEMA({})
kwargs[CONF_ALEXA] = alexa_sh.Config(
should_expose=alexa_conf[CONF_FILTER],
entity_config=alexa_conf.get(CONF_ENTITY_CONFIG),
)
kwargs['gactions_should_expose'] = gactions_conf[CONF_FILTER]
cloud = hass.data[DOMAIN] = Cloud(hass, **kwargs)
success = yield from cloud.initialize()
@ -101,14 +113,14 @@ def async_setup(hass, config):
class Cloud:
"""Store the configuration of the cloud connection."""
def __init__(self, hass, mode, alexa, gactions_should_expose,
def __init__(self, hass, mode, alexa, google_actions,
cognito_client_id=None, user_pool_id=None, region=None,
relayer=None):
"""Create an instance of Cloud."""
self.hass = hass
self.mode = mode
self.alexa_config = alexa
self._gactions_should_expose = gactions_should_expose
self._google_actions = google_actions
self._gactions_config = None
self.jwt_keyset = None
self.id_token = None
@ -161,13 +173,16 @@ class Cloud:
def gactions_config(self):
"""Return the Google Assistant config."""
if self._gactions_config is None:
conf = self._google_actions
def should_expose(entity):
"""If an entity should be exposed."""
return self._gactions_should_expose(entity.entity_id)
return conf['filter'](entity.entity_id)
self._gactions_config = ga_sh.Config(
should_expose=should_expose,
agent_user_id=self.claims['cognito:username']
agent_user_id=self.claims['cognito:username'],
entity_config=conf.get(CONF_ENTITY_CONFIG),
)
return self._gactions_config

View File

@ -17,6 +17,7 @@ import voluptuous as vol
from homeassistant.core import HomeAssistant # NOQA
from typing import Dict, Any # NOQA
from homeassistant.const import CONF_NAME, CONF_TYPE
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.loader import bind_hass
@ -25,10 +26,12 @@ from .const import (
DOMAIN, CONF_PROJECT_ID, CONF_CLIENT_ID, CONF_ACCESS_TOKEN,
CONF_EXPOSE_BY_DEFAULT, DEFAULT_EXPOSE_BY_DEFAULT, CONF_EXPOSED_DOMAINS,
DEFAULT_EXPOSED_DOMAINS, CONF_AGENT_USER_ID, CONF_API_KEY,
SERVICE_REQUEST_SYNC, REQUEST_SYNC_BASE_URL
SERVICE_REQUEST_SYNC, REQUEST_SYNC_BASE_URL, CONF_ENTITY_CONFIG,
CONF_EXPOSE, CONF_ALIASES
)
from .auth import GoogleAssistantAuthView
from .http import async_register_http
from .smart_home import MAPPING_COMPONENT
_LOGGER = logging.getLogger(__name__)
@ -36,6 +39,13 @@ DEPENDENCIES = ['http']
DEFAULT_AGENT_USER_ID = 'home-assistant'
ENTITY_SCHEMA = vol.Schema({
vol.Optional(CONF_NAME): cv.string,
vol.Optional(CONF_TYPE): vol.In(MAPPING_COMPONENT),
vol.Optional(CONF_EXPOSE): cv.boolean,
vol.Optional(CONF_ALIASES): vol.All(cv.ensure_list, [cv.string])
})
CONFIG_SCHEMA = vol.Schema(
{
DOMAIN: {
@ -48,7 +58,8 @@ CONFIG_SCHEMA = vol.Schema(
default=DEFAULT_EXPOSED_DOMAINS): cv.ensure_list,
vol.Optional(CONF_AGENT_USER_ID,
default=DEFAULT_AGENT_USER_ID): cv.string,
vol.Optional(CONF_API_KEY): cv.string
vol.Optional(CONF_API_KEY): cv.string,
vol.Optional(CONF_ENTITY_CONFIG): {cv.entity_id: ENTITY_SCHEMA}
}
},
extra=vol.ALLOW_EXTRA)

View File

@ -6,10 +6,10 @@ import logging
# Typing imports
# pylint: disable=using-constant-test,unused-import,ungrouped-imports
# if False:
from homeassistant.core import HomeAssistant # NOQA
from aiohttp.web import Request, Response # NOQA
from typing import Dict, Any # NOQA
from homeassistant.core import HomeAssistant # NOQA
from homeassistant.components.http import HomeAssistantView
from homeassistant.const import (
HTTP_BAD_REQUEST,

View File

@ -3,10 +3,8 @@ DOMAIN = 'google_assistant'
GOOGLE_ASSISTANT_API_ENDPOINT = '/api/google_assistant'
ATTR_GOOGLE_ASSISTANT = 'google_assistant'
ATTR_GOOGLE_ASSISTANT_NAME = 'google_assistant_name'
ATTR_GOOGLE_ASSISTANT_TYPE = 'google_assistant_type'
CONF_EXPOSE = 'expose'
CONF_ENTITY_CONFIG = 'entity_config'
CONF_EXPOSE_BY_DEFAULT = 'expose_by_default'
CONF_EXPOSED_DOMAINS = 'exposed_domains'
CONF_PROJECT_ID = 'project_id'

View File

@ -23,8 +23,9 @@ from .const import (
CONF_ACCESS_TOKEN,
CONF_EXPOSE_BY_DEFAULT,
CONF_EXPOSED_DOMAINS,
ATTR_GOOGLE_ASSISTANT,
CONF_AGENT_USER_ID
CONF_AGENT_USER_ID,
CONF_ENTITY_CONFIG,
CONF_EXPOSE,
)
from .smart_home import async_handle_message, Config
@ -38,6 +39,7 @@ def async_register_http(hass, cfg):
expose_by_default = cfg.get(CONF_EXPOSE_BY_DEFAULT)
exposed_domains = cfg.get(CONF_EXPOSED_DOMAINS)
agent_user_id = cfg.get(CONF_AGENT_USER_ID)
entity_config = cfg.get(CONF_ENTITY_CONFIG)
def is_exposed(entity) -> bool:
"""Determine if an entity should be exposed to Google Assistant."""
@ -45,11 +47,11 @@ def async_register_http(hass, cfg):
# Ignore entities that are views
return False
domain = entity.domain.lower()
explicit_expose = entity.attributes.get(ATTR_GOOGLE_ASSISTANT, None)
explicit_expose = \
entity_config.get(entity.entity_id, {}).get(CONF_EXPOSE)
domain_exposed_by_default = \
expose_by_default and domain in exposed_domains
expose_by_default and entity.domain in exposed_domains
# Expose an entity if the entity's domain is exposed by default and
# the configuration doesn't explicitly exclude it from being
@ -59,7 +61,7 @@ def async_register_http(hass, cfg):
return is_default_exposed or explicit_expose
gass_config = Config(is_exposed, agent_user_id)
gass_config = Config(is_exposed, agent_user_id, entity_config)
hass.http.register_view(
GoogleAssistantView(access_token, gass_config))

View File

@ -1,6 +1,5 @@
"""Support for Google Assistant Smart Home API."""
import asyncio
from collections import namedtuple
import logging
# Typing imports
@ -16,9 +15,9 @@ from homeassistant.util.decorator import Registry
from homeassistant.const import (
ATTR_SUPPORTED_FEATURES, ATTR_ENTITY_ID,
CONF_FRIENDLY_NAME, STATE_OFF,
SERVICE_TURN_OFF, SERVICE_TURN_ON,
STATE_OFF, SERVICE_TURN_OFF, SERVICE_TURN_ON,
TEMP_FAHRENHEIT, TEMP_CELSIUS,
CONF_NAME, CONF_TYPE
)
from homeassistant.components import (
switch, light, cover, media_player, group, fan, scene, script, climate
@ -26,8 +25,7 @@ from homeassistant.components import (
from homeassistant.util.unit_system import METRIC_SYSTEM
from .const import (
ATTR_GOOGLE_ASSISTANT_NAME, COMMAND_COLOR,
ATTR_GOOGLE_ASSISTANT_TYPE,
COMMAND_COLOR,
COMMAND_BRIGHTNESS, COMMAND_ONOFF, COMMAND_ACTIVATESCENE,
COMMAND_THERMOSTAT_TEMPERATURE_SETPOINT,
COMMAND_THERMOSTAT_TEMPERATURE_SET_RANGE, COMMAND_THERMOSTAT_SET_MODE,
@ -69,13 +67,22 @@ MAPPING_COMPONENT = {
} # type: Dict[str, list]
Config = namedtuple('GoogleAssistantConfig', 'should_expose,agent_user_id')
class Config:
"""Hold the configuration for Google Assistant."""
def __init__(self, should_expose, agent_user_id, entity_config=None):
"""Initialize the configuration."""
self.should_expose = should_expose
self.agent_user_id = agent_user_id
self.entity_config = entity_config or {}
def entity_to_device(entity: Entity, units: UnitSystem):
def entity_to_device(entity: Entity, config: Config, units: UnitSystem):
"""Convert a hass entity into an google actions device."""
entity_config = config.entity_config.get(entity.entity_id, {})
class_data = MAPPING_COMPONENT.get(
entity.attributes.get(ATTR_GOOGLE_ASSISTANT_TYPE) or entity.domain)
entity_config.get(CONF_TYPE) or entity.domain)
if class_data is None:
return None
@ -90,17 +97,12 @@ def entity_to_device(entity: Entity, units: UnitSystem):
device['traits'].append(class_data[1])
# handle custom names
device['name']['name'] = \
entity.attributes.get(ATTR_GOOGLE_ASSISTANT_NAME) or \
entity.attributes.get(CONF_FRIENDLY_NAME)
device['name']['name'] = entity_config.get(CONF_NAME) or entity.name
# use aliases
aliases = entity.attributes.get(CONF_ALIASES)
aliases = entity_config.get(CONF_ALIASES)
if aliases:
if isinstance(aliases, list):
device['name']['nicknames'] = aliases
else:
_LOGGER.warning("%s must be a list", CONF_ALIASES)
device['name']['nicknames'] = aliases
# add trait if entity supports feature
if class_data[2]:
@ -322,7 +324,7 @@ def async_devices_sync(hass, config, payload):
if not config.should_expose(entity):
continue
device = entity_to_device(entity, hass.config.units)
device = entity_to_device(entity, config, hass.config.units)
if device is None:
_LOGGER.warning("No mapping for %s domain", entity.domain)
continue

View File

@ -317,6 +317,13 @@ def test_handler_google_actions(hass):
'filter': {
'exclude_entities': 'switch.test2'
},
'entity_config': {
'switch.test': {
'name': 'Config name',
'type': 'light',
'aliases': 'Config alias'
}
}
}
}
})
@ -340,4 +347,6 @@ def test_handler_google_actions(hass):
device = devices[0]
assert device['id'] == 'switch.test'
assert device['name']['name'] == 'Test switch'
assert device['name']['name'] == 'Config name'
assert device['name']['nicknames'] == ['Config alias']
assert device['type'] == 'action.devices.types.LIGHT'

View File

@ -36,6 +36,15 @@ def assistant_client(loop, hass, test_client):
'project_id': PROJECT_ID,
'client_id': CLIENT_ID,
'access_token': ACCESS_TOKEN,
'entity_config': {
'light.ceiling_lights': {
'aliases': ['top lights', 'ceiling lights'],
'name': 'Roof Lights',
},
'switch.decorative_lights': {
'type': 'light'
}
}
}
}))
@ -88,26 +97,6 @@ def hass_fixture(loop, hass):
}]
}))
# Kitchen light is explicitly excluded from being exposed
ceiling_lights_entity = hass.states.get('light.ceiling_lights')
attrs = dict(ceiling_lights_entity.attributes)
attrs[ga.const.ATTR_GOOGLE_ASSISTANT_NAME] = "Roof Lights"
attrs[ga.const.CONF_ALIASES] = ['top lights', 'ceiling lights']
hass.states.async_set(
ceiling_lights_entity.entity_id,
ceiling_lights_entity.state,
attributes=attrs)
# By setting the google_assistant_type = 'light'
# we can override how a device is reported to GA
switch_light = hass.states.get('switch.decorative_lights')
attrs = dict(switch_light.attributes)
attrs[ga.const.ATTR_GOOGLE_ASSISTANT_TYPE] = "light"
hass.states.async_set(
switch_light.entity_id,
switch_light.state,
attributes=attrs)
return hass