Move Google Assistant entity config out of customize (#11499)
* Move Google Assistant entity config out of customize * CONF_ALIAS -> CONF_ALIASES * Lintpull/11557/head
parent
13042d5557
commit
8313225b40
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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))
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue