Avoid exposing unsupported entities to Alexa (#92107)

* Avoid exposing unsupported entities to Alexa

* Update homeassistant/components/cloud/alexa_config.py

Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>

---------

Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
This commit is contained in:
Erik Montnemery 2023-04-27 13:31:24 +02:00 committed by GitHub
parent 21d887dd04
commit 7215f6320e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 170 additions and 2 deletions

View File

@ -20,14 +20,17 @@ from homeassistant.components.alexa import (
errors as alexa_errors,
state_report as alexa_state_report,
)
from homeassistant.components.binary_sensor import BinarySensorDeviceClass
from homeassistant.components.homeassistant.exposed_entities import (
async_get_assistant_settings,
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 HomeAssistant, callback, split_entity_id
from homeassistant.helpers import entity_registry as er, start
from homeassistant.helpers.entity import get_device_class
from homeassistant.helpers.event import async_call_later
from homeassistant.setup import async_setup_component
from homeassistant.util.dt import utcnow
@ -51,6 +54,69 @@ CLOUD_ALEXA = f"{CLOUD_DOMAIN}.{ALEXA_DOMAIN}"
SYNC_DELAY = 1
SUPPORTED_DOMAINS = {
"alarm_control_panel",
"alert",
"automation",
"button",
"camera",
"climate",
"cover",
"fan",
"group",
"humidifier",
"image_processing",
"input_boolean",
"input_button",
"input_number",
"light",
"lock",
"media_player",
"number",
"scene",
"script",
"switch",
"timer",
"vacuum",
}
SUPPORTED_BINARY_SENSOR_DEVICE_CLASSES = {
BinarySensorDeviceClass.DOOR,
BinarySensorDeviceClass.GARAGE_DOOR,
BinarySensorDeviceClass.MOTION,
BinarySensorDeviceClass.OPENING,
BinarySensorDeviceClass.PRESENCE,
BinarySensorDeviceClass.WINDOW,
}
SUPPORTED_SENSOR_DEVICE_CLASSES = {
SensorDeviceClass.TEMPERATURE,
}
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 CloudAlexaConfig(alexa_config.AbstractConfig):
"""Alexa Configuration."""
@ -183,9 +249,13 @@ class CloudAlexaConfig(alexa_config.AbstractConfig):
# Backwards compat
if (default_expose := self._prefs.alexa_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(self, entity_id):
"""If an entity should be exposed."""

View File

@ -650,3 +650,101 @@ async def test_alexa_config_migrate_expose_entity_prefs_default_none(
entity_default = entity_registry.async_get(entity_default.entity_id)
assert entity_default.options == {"cloud.alexa": {"should_expose": True}}
async def test_alexa_config_migrate_expose_entity_prefs_default(
hass: HomeAssistant,
cloud_prefs: CloudPreferences,
cloud_stub,
entity_registry: er.EntityRegistry,
) -> None:
"""Test migrating Alexa 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(
alexa_enabled=True,
alexa_report_state=False,
alexa_settings_version=1,
)
cloud_prefs._prefs[PREF_ALEXA_DEFAULT_EXPOSE] = [
"binary_sensor",
"light",
"sensor",
"water_heater",
]
conf = alexa_config.CloudAlexaConfig(
hass, ALEXA_SCHEMA({}), "mock-user-id", cloud_prefs, cloud_stub
)
await conf.async_initialize()
binary_sensor_supported = entity_registry.async_get(
binary_sensor_supported.entity_id
)
assert binary_sensor_supported.options == {"cloud.alexa": {"should_expose": True}}
binary_sensor_unsupported = entity_registry.async_get(
binary_sensor_unsupported.entity_id
)
assert binary_sensor_unsupported.options == {
"cloud.alexa": {"should_expose": False}
}
light = entity_registry.async_get(light.entity_id)
assert light.options == {"cloud.alexa": {"should_expose": True}}
sensor_supported = entity_registry.async_get(sensor_supported.entity_id)
assert sensor_supported.options == {"cloud.alexa": {"should_expose": True}}
sensor_unsupported = entity_registry.async_get(sensor_unsupported.entity_id)
assert sensor_unsupported.options == {"cloud.alexa": {"should_expose": False}}
water_heater = entity_registry.async_get(water_heater.entity_id)
assert water_heater.options == {"cloud.alexa": {"should_expose": False}}