Avoid exposing unsupported entities to Google Assistant (#92105)

* Avoid exposing unsupported entities to Google Assistant

* Add Google Assistant specific support sets

* Add test
This commit is contained in:
Erik Montnemery 2023-04-27 10:38:21 +02:00 committed by GitHub
parent a164530a64
commit 65c9d4a4ae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 179 additions and 2 deletions

View File

@ -7,12 +7,14 @@ from typing import Any
from hass_nabucasa import Cloud, cloud_api
from hass_nabucasa.google_report_state import ErrorResponse
from homeassistant.components.binary_sensor import BinarySensorDeviceClass
from homeassistant.components.google_assistant import DOMAIN as GOOGLE_DOMAIN
from homeassistant.components.google_assistant.helpers import AbstractConfig
from homeassistant.components.homeassistant.exposed_entities import (
async_listen_entity_updates,
async_should_expose,
)
from homeassistant.components.sensor import SensorDeviceClass
from homeassistant.const import CLOUD_NEVER_EXPOSED_ENTITIES
from homeassistant.core import (
CoreState,
@ -22,6 +24,7 @@ from homeassistant.core import (
split_entity_id,
)
from homeassistant.helpers import device_registry as dr, entity_registry as er, start
from homeassistant.helpers.entity import get_device_class
from homeassistant.setup import async_setup_component
from .const import (
@ -39,6 +42,73 @@ _LOGGER = logging.getLogger(__name__)
CLOUD_GOOGLE = f"{CLOUD_DOMAIN}.{GOOGLE_DOMAIN}"
SUPPORTED_DOMAINS = {
"alarm_control_panel",
"button",
"camera",
"climate",
"cover",
"fan",
"group",
"humidifier",
"input_boolean",
"input_button",
"input_select",
"light",
"lock",
"media_player",
"scene",
"script",
"select",
"switch",
"vacuum",
}
SUPPORTED_BINARY_SENSOR_DEVICE_CLASSES = {
BinarySensorDeviceClass.DOOR,
BinarySensorDeviceClass.GARAGE_DOOR,
BinarySensorDeviceClass.LOCK,
BinarySensorDeviceClass.MOTION,
BinarySensorDeviceClass.OPENING,
BinarySensorDeviceClass.PRESENCE,
BinarySensorDeviceClass.WINDOW,
}
SUPPORTED_SENSOR_DEVICE_CLASSES = {
SensorDeviceClass.AQI,
SensorDeviceClass.CO,
SensorDeviceClass.CO2,
SensorDeviceClass.HUMIDITY,
SensorDeviceClass.PM10,
SensorDeviceClass.PM25,
SensorDeviceClass.TEMPERATURE,
SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS,
}
def _supported_legacy(hass: HomeAssistant, entity_id: str) -> bool:
"""Return if the entity is supported.
This is called when migrating from legacy config format to avoid exposing
all binary sensors and sensors.
"""
domain = split_entity_id(entity_id)[0]
if domain in SUPPORTED_DOMAINS:
return True
device_class = get_device_class(hass, entity_id)
if (
domain == "binary_sensor"
and device_class in SUPPORTED_BINARY_SENSOR_DEVICE_CLASSES
):
return True
if domain == "sensor" and device_class in SUPPORTED_SENSOR_DEVICE_CLASSES:
return True
return False
class CloudGoogleConfig(AbstractConfig):
"""HA Cloud Configuration for Google Assistant."""
@ -180,9 +250,13 @@ class CloudGoogleConfig(AbstractConfig):
# Backwards compat
if default_expose is None:
return not auxiliary_entity
return not auxiliary_entity and _supported_legacy(self.hass, entity_id)
return not auxiliary_entity and split_entity_id(entity_id)[0] in default_expose
return (
not auxiliary_entity
and split_entity_id(entity_id)[0] in default_expose
and _supported_legacy(self.hass, entity_id)
)
def _should_expose_entity_id(self, entity_id):
"""If an entity should be exposed."""

View File

@ -611,3 +611,106 @@ async def test_google_config_migrate_expose_entity_prefs_default_none(
entity_default = entity_registry.async_get(entity_default.entity_id)
assert entity_default.options == {"cloud.google_assistant": {"should_expose": True}}
async def test_google_config_migrate_expose_entity_prefs_default(
hass: HomeAssistant,
cloud_prefs: CloudPreferences,
entity_registry: er.EntityRegistry,
) -> None:
"""Test migrating Google entity config."""
assert await async_setup_component(hass, "homeassistant", {})
binary_sensor_supported = entity_registry.async_get_or_create(
"binary_sensor",
"test",
"binary_sensor_supported",
original_device_class="door",
suggested_object_id="supported",
)
binary_sensor_unsupported = entity_registry.async_get_or_create(
"binary_sensor",
"test",
"binary_sensor_unsupported",
original_device_class="battery",
suggested_object_id="unsupported",
)
light = entity_registry.async_get_or_create(
"light",
"test",
"unique",
suggested_object_id="light",
)
sensor_supported = entity_registry.async_get_or_create(
"sensor",
"test",
"sensor_supported",
original_device_class="temperature",
suggested_object_id="supported",
)
sensor_unsupported = entity_registry.async_get_or_create(
"sensor",
"test",
"sensor_unsupported",
original_device_class="battery",
suggested_object_id="unsupported",
)
water_heater = entity_registry.async_get_or_create(
"water_heater",
"test",
"unique",
suggested_object_id="water_heater",
)
await cloud_prefs.async_update(
google_enabled=True,
google_report_state=False,
google_settings_version=1,
)
cloud_prefs._prefs[PREF_GOOGLE_DEFAULT_EXPOSE] = [
"binary_sensor",
"light",
"sensor",
"water_heater",
]
conf = CloudGoogleConfig(
hass, GACTIONS_SCHEMA({}), "mock-user-id", cloud_prefs, Mock(is_logged_in=False)
)
await conf.async_initialize()
binary_sensor_supported = entity_registry.async_get(
binary_sensor_supported.entity_id
)
assert binary_sensor_supported.options == {
"cloud.google_assistant": {"should_expose": True}
}
binary_sensor_unsupported = entity_registry.async_get(
binary_sensor_unsupported.entity_id
)
assert binary_sensor_unsupported.options == {
"cloud.google_assistant": {"should_expose": False}
}
light = entity_registry.async_get(light.entity_id)
assert light.options == {"cloud.google_assistant": {"should_expose": True}}
sensor_supported = entity_registry.async_get(sensor_supported.entity_id)
assert sensor_supported.options == {
"cloud.google_assistant": {"should_expose": True}
}
sensor_unsupported = entity_registry.async_get(sensor_unsupported.entity_id)
assert sensor_unsupported.options == {
"cloud.google_assistant": {"should_expose": False}
}
water_heater = entity_registry.async_get(water_heater.entity_id)
assert water_heater.options == {"cloud.google_assistant": {"should_expose": False}}