Allow exposing entities not in the entity registry to assistants (#92363)
This commit is contained in:
parent
cc4e741cfa
commit
e3c16e634b
52 changed files with 563 additions and 224 deletions
|
@ -84,8 +84,7 @@ class AbstractConfig(ABC):
|
||||||
unsub_func()
|
unsub_func()
|
||||||
self._unsub_proactive_report = None
|
self._unsub_proactive_report = None
|
||||||
|
|
||||||
@callback
|
async def should_expose(self, entity_id):
|
||||||
def should_expose(self, entity_id):
|
|
||||||
"""If an entity should be exposed."""
|
"""If an entity should be exposed."""
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
|
@ -103,7 +103,7 @@ async def async_api_discovery(
|
||||||
discovery_endpoints = [
|
discovery_endpoints = [
|
||||||
alexa_entity.serialize_discovery()
|
alexa_entity.serialize_discovery()
|
||||||
for alexa_entity in async_get_entities(hass, config)
|
for alexa_entity in async_get_entities(hass, config)
|
||||||
if config.should_expose(alexa_entity.entity_id)
|
if await config.should_expose(alexa_entity.entity_id)
|
||||||
]
|
]
|
||||||
|
|
||||||
return directive.response(
|
return directive.response(
|
||||||
|
|
|
@ -30,7 +30,7 @@ class AlexaDirective:
|
||||||
|
|
||||||
self.entity = self.entity_id = self.endpoint = self.instance = None
|
self.entity = self.entity_id = self.endpoint = self.instance = None
|
||||||
|
|
||||||
def load_entity(self, hass, config):
|
async def load_entity(self, hass, config):
|
||||||
"""Set attributes related to the entity for this request.
|
"""Set attributes related to the entity for this request.
|
||||||
|
|
||||||
Sets these attributes when self.has_endpoint is True:
|
Sets these attributes when self.has_endpoint is True:
|
||||||
|
@ -49,7 +49,7 @@ class AlexaDirective:
|
||||||
self.entity_id = _endpoint_id.replace("#", ".")
|
self.entity_id = _endpoint_id.replace("#", ".")
|
||||||
|
|
||||||
self.entity = hass.states.get(self.entity_id)
|
self.entity = hass.states.get(self.entity_id)
|
||||||
if not self.entity or not config.should_expose(self.entity_id):
|
if not self.entity or not await config.should_expose(self.entity_id):
|
||||||
raise AlexaInvalidEndpointError(_endpoint_id)
|
raise AlexaInvalidEndpointError(_endpoint_id)
|
||||||
|
|
||||||
self.endpoint = ENTITY_ADAPTERS[self.entity.domain](hass, config, self.entity)
|
self.endpoint = ENTITY_ADAPTERS[self.entity.domain](hass, config, self.entity)
|
||||||
|
|
|
@ -34,7 +34,7 @@ async def async_handle_message(hass, config, request, context=None, enabled=True
|
||||||
await config.set_authorized(True)
|
await config.set_authorized(True)
|
||||||
|
|
||||||
if directive.has_endpoint:
|
if directive.has_endpoint:
|
||||||
directive.load_entity(hass, config)
|
await directive.load_entity(hass, config)
|
||||||
|
|
||||||
funct_ref = HANDLERS.get((directive.namespace, directive.name))
|
funct_ref = HANDLERS.get((directive.namespace, directive.name))
|
||||||
if funct_ref:
|
if funct_ref:
|
||||||
|
|
|
@ -60,7 +60,7 @@ class AlexaConfig(AbstractConfig):
|
||||||
"""Return an identifier for the user that represents this config."""
|
"""Return an identifier for the user that represents this config."""
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
def should_expose(self, entity_id):
|
async def should_expose(self, entity_id):
|
||||||
"""If an entity should be exposed."""
|
"""If an entity should be exposed."""
|
||||||
if not self._config[CONF_FILTER].empty_filter:
|
if not self._config[CONF_FILTER].empty_filter:
|
||||||
return self._config[CONF_FILTER](entity_id)
|
return self._config[CONF_FILTER](entity_id)
|
||||||
|
|
|
@ -64,7 +64,7 @@ async def async_enable_proactive_mode(hass, smart_home_config):
|
||||||
if new_state.domain not in ENTITY_ADAPTERS:
|
if new_state.domain not in ENTITY_ADAPTERS:
|
||||||
return
|
return
|
||||||
|
|
||||||
if not smart_home_config.should_expose(changed_entity):
|
if not await smart_home_config.should_expose(changed_entity):
|
||||||
_LOGGER.debug("Not exposing %s because filtered by config", changed_entity)
|
_LOGGER.debug("Not exposing %s because filtered by config", changed_entity)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
|
@ -257,14 +257,14 @@ class CloudAlexaConfig(alexa_config.AbstractConfig):
|
||||||
and entity_supported(self.hass, entity_id)
|
and entity_supported(self.hass, entity_id)
|
||||||
)
|
)
|
||||||
|
|
||||||
def should_expose(self, entity_id):
|
async def should_expose(self, entity_id):
|
||||||
"""If an entity should be exposed."""
|
"""If an entity should be exposed."""
|
||||||
if not self._config[CONF_FILTER].empty_filter:
|
if not self._config[CONF_FILTER].empty_filter:
|
||||||
if entity_id in CLOUD_NEVER_EXPOSED_ENTITIES:
|
if entity_id in CLOUD_NEVER_EXPOSED_ENTITIES:
|
||||||
return False
|
return False
|
||||||
return self._config[CONF_FILTER](entity_id)
|
return self._config[CONF_FILTER](entity_id)
|
||||||
|
|
||||||
return async_should_expose(self.hass, CLOUD_ALEXA, entity_id)
|
return await async_should_expose(self.hass, CLOUD_ALEXA, entity_id)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def async_invalidate_access_token(self):
|
def async_invalidate_access_token(self):
|
||||||
|
@ -423,7 +423,7 @@ class CloudAlexaConfig(alexa_config.AbstractConfig):
|
||||||
is_enabled = self.enabled
|
is_enabled = self.enabled
|
||||||
|
|
||||||
for entity in alexa_entities.async_get_entities(self.hass, self):
|
for entity in alexa_entities.async_get_entities(self.hass, self):
|
||||||
if is_enabled and self.should_expose(entity.entity_id):
|
if is_enabled and await self.should_expose(entity.entity_id):
|
||||||
to_update.append(entity.entity_id)
|
to_update.append(entity.entity_id)
|
||||||
else:
|
else:
|
||||||
to_remove.append(entity.entity_id)
|
to_remove.append(entity.entity_id)
|
||||||
|
@ -482,7 +482,7 @@ class CloudAlexaConfig(alexa_config.AbstractConfig):
|
||||||
|
|
||||||
entity_id = event.data["entity_id"]
|
entity_id = event.data["entity_id"]
|
||||||
|
|
||||||
if not self.should_expose(entity_id):
|
if not await self.should_expose(entity_id):
|
||||||
return
|
return
|
||||||
|
|
||||||
action = event.data["action"]
|
action = event.data["action"]
|
||||||
|
|
|
@ -222,9 +222,9 @@ class CloudGoogleConfig(AbstractConfig):
|
||||||
self._handle_device_registry_updated,
|
self._handle_device_registry_updated,
|
||||||
)
|
)
|
||||||
|
|
||||||
def should_expose(self, state):
|
async def should_expose(self, state):
|
||||||
"""If a state object should be exposed."""
|
"""If a state object should be exposed."""
|
||||||
return self._should_expose_entity_id(state.entity_id)
|
return await self._should_expose_entity_id(state.entity_id)
|
||||||
|
|
||||||
def _should_expose_legacy(self, entity_id):
|
def _should_expose_legacy(self, entity_id):
|
||||||
"""If an entity ID should be exposed."""
|
"""If an entity ID should be exposed."""
|
||||||
|
@ -258,14 +258,14 @@ class CloudGoogleConfig(AbstractConfig):
|
||||||
and _supported_legacy(self.hass, entity_id)
|
and _supported_legacy(self.hass, entity_id)
|
||||||
)
|
)
|
||||||
|
|
||||||
def _should_expose_entity_id(self, entity_id):
|
async def _should_expose_entity_id(self, entity_id):
|
||||||
"""If an entity should be exposed."""
|
"""If an entity should be exposed."""
|
||||||
if not self._config[CONF_FILTER].empty_filter:
|
if not self._config[CONF_FILTER].empty_filter:
|
||||||
if entity_id in CLOUD_NEVER_EXPOSED_ENTITIES:
|
if entity_id in CLOUD_NEVER_EXPOSED_ENTITIES:
|
||||||
return False
|
return False
|
||||||
return self._config[CONF_FILTER](entity_id)
|
return self._config[CONF_FILTER](entity_id)
|
||||||
|
|
||||||
return async_should_expose(self.hass, CLOUD_GOOGLE, entity_id)
|
return await async_should_expose(self.hass, CLOUD_GOOGLE, entity_id)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def agent_user_id(self):
|
def agent_user_id(self):
|
||||||
|
@ -358,8 +358,7 @@ class CloudGoogleConfig(AbstractConfig):
|
||||||
"""Handle updated preferences."""
|
"""Handle updated preferences."""
|
||||||
self.async_schedule_google_sync_all()
|
self.async_schedule_google_sync_all()
|
||||||
|
|
||||||
@callback
|
async def _handle_entity_registry_updated(self, event: Event) -> None:
|
||||||
def _handle_entity_registry_updated(self, event: Event) -> None:
|
|
||||||
"""Handle when entity registry updated."""
|
"""Handle when entity registry updated."""
|
||||||
if (
|
if (
|
||||||
not self.enabled
|
not self.enabled
|
||||||
|
@ -376,13 +375,12 @@ class CloudGoogleConfig(AbstractConfig):
|
||||||
|
|
||||||
entity_id = event.data["entity_id"]
|
entity_id = event.data["entity_id"]
|
||||||
|
|
||||||
if not self._should_expose_entity_id(entity_id):
|
if not await self._should_expose_entity_id(entity_id):
|
||||||
return
|
return
|
||||||
|
|
||||||
self.async_schedule_google_sync_all()
|
self.async_schedule_google_sync_all()
|
||||||
|
|
||||||
@callback
|
async def _handle_device_registry_updated(self, event: Event) -> None:
|
||||||
def _handle_device_registry_updated(self, event: Event) -> None:
|
|
||||||
"""Handle when device registry updated."""
|
"""Handle when device registry updated."""
|
||||||
if (
|
if (
|
||||||
not self.enabled
|
not self.enabled
|
||||||
|
@ -396,13 +394,15 @@ class CloudGoogleConfig(AbstractConfig):
|
||||||
return
|
return
|
||||||
|
|
||||||
# Check if any exposed entity uses the device area
|
# Check if any exposed entity uses the device area
|
||||||
if not any(
|
used = False
|
||||||
entity_entry.area_id is None
|
|
||||||
and self._should_expose_entity_id(entity_entry.entity_id)
|
|
||||||
for entity_entry in er.async_entries_for_device(
|
for entity_entry in er.async_entries_for_device(
|
||||||
er.async_get(self.hass), event.data["device_id"]
|
er.async_get(self.hass), event.data["device_id"]
|
||||||
)
|
|
||||||
):
|
):
|
||||||
|
if entity_entry.area_id is None and await self._should_expose_entity_id(
|
||||||
|
entity_entry.entity_id
|
||||||
|
):
|
||||||
|
used = True
|
||||||
|
if not used:
|
||||||
return
|
return
|
||||||
|
|
||||||
self.async_schedule_google_sync_all()
|
self.async_schedule_google_sync_all()
|
||||||
|
|
|
@ -94,7 +94,7 @@ CONFIG_SCHEMA = vol.Schema(
|
||||||
def _get_agent_manager(hass: HomeAssistant) -> AgentManager:
|
def _get_agent_manager(hass: HomeAssistant) -> AgentManager:
|
||||||
"""Get the active agent."""
|
"""Get the active agent."""
|
||||||
manager = AgentManager(hass)
|
manager = AgentManager(hass)
|
||||||
manager.async_setup()
|
hass.async_create_task(manager.async_setup())
|
||||||
return manager
|
return manager
|
||||||
|
|
||||||
|
|
||||||
|
@ -393,9 +393,9 @@ class AgentManager:
|
||||||
self._agents: dict[str, AbstractConversationAgent] = {}
|
self._agents: dict[str, AbstractConversationAgent] = {}
|
||||||
self._builtin_agent_init_lock = asyncio.Lock()
|
self._builtin_agent_init_lock = asyncio.Lock()
|
||||||
|
|
||||||
def async_setup(self) -> None:
|
async def async_setup(self) -> None:
|
||||||
"""Set up the conversation agents."""
|
"""Set up the conversation agents."""
|
||||||
async_setup_default_agent(self.hass)
|
await async_setup_default_agent(self.hass)
|
||||||
|
|
||||||
async def async_get_agent(
|
async def async_get_agent(
|
||||||
self, agent_id: str | None = None
|
self, agent_id: str | None = None
|
||||||
|
|
|
@ -73,23 +73,20 @@ def _get_language_variations(language: str) -> Iterable[str]:
|
||||||
yield lang
|
yield lang
|
||||||
|
|
||||||
|
|
||||||
@core.callback
|
async def async_setup(hass: core.HomeAssistant) -> None:
|
||||||
def async_setup(hass: core.HomeAssistant) -> None:
|
|
||||||
"""Set up entity registry listener for the default agent."""
|
"""Set up entity registry listener for the default agent."""
|
||||||
entity_registry = er.async_get(hass)
|
entity_registry = er.async_get(hass)
|
||||||
for entity_id in entity_registry.entities:
|
for entity_id in entity_registry.entities:
|
||||||
async_should_expose(hass, DOMAIN, entity_id)
|
await async_should_expose(hass, DOMAIN, entity_id)
|
||||||
|
|
||||||
@core.callback
|
async def async_handle_entity_registry_changed(event: core.Event) -> None:
|
||||||
def async_handle_entity_registry_changed(event: core.Event) -> None:
|
|
||||||
"""Set expose flag on newly created entities."""
|
"""Set expose flag on newly created entities."""
|
||||||
if event.data["action"] == "create":
|
if event.data["action"] == "create":
|
||||||
async_should_expose(hass, DOMAIN, event.data["entity_id"])
|
await async_should_expose(hass, DOMAIN, event.data["entity_id"])
|
||||||
|
|
||||||
hass.bus.async_listen(
|
hass.bus.async_listen(
|
||||||
er.EVENT_ENTITY_REGISTRY_UPDATED,
|
er.EVENT_ENTITY_REGISTRY_UPDATED,
|
||||||
async_handle_entity_registry_changed,
|
async_handle_entity_registry_changed,
|
||||||
run_immediately=True,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -157,7 +154,7 @@ class DefaultAgent(AbstractConversationAgent):
|
||||||
conversation_id,
|
conversation_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
slot_lists = self._make_slot_lists()
|
slot_lists = await self._make_slot_lists()
|
||||||
|
|
||||||
result = await self.hass.async_add_executor_job(
|
result = await self.hass.async_add_executor_job(
|
||||||
self._recognize,
|
self._recognize,
|
||||||
|
@ -486,7 +483,7 @@ class DefaultAgent(AbstractConversationAgent):
|
||||||
"""Handle updated preferences."""
|
"""Handle updated preferences."""
|
||||||
self._slot_lists = None
|
self._slot_lists = None
|
||||||
|
|
||||||
def _make_slot_lists(self) -> dict[str, SlotList]:
|
async def _make_slot_lists(self) -> dict[str, SlotList]:
|
||||||
"""Create slot lists with areas and entity names/aliases."""
|
"""Create slot lists with areas and entity names/aliases."""
|
||||||
if self._slot_lists is not None:
|
if self._slot_lists is not None:
|
||||||
return self._slot_lists
|
return self._slot_lists
|
||||||
|
@ -496,7 +493,7 @@ class DefaultAgent(AbstractConversationAgent):
|
||||||
entities = [
|
entities = [
|
||||||
entity
|
entity
|
||||||
for entity in entity_registry.entities.values()
|
for entity in entity_registry.entities.values()
|
||||||
if async_should_expose(self.hass, DOMAIN, entity.entity_id)
|
if await async_should_expose(self.hass, DOMAIN, entity.entity_id)
|
||||||
]
|
]
|
||||||
devices = dr.async_get(self.hass)
|
devices = dr.async_get(self.hass)
|
||||||
|
|
||||||
|
|
|
@ -175,7 +175,7 @@ class AbstractConfig(ABC):
|
||||||
"""Get agent user ID from context."""
|
"""Get agent user ID from context."""
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def should_expose(self, state) -> bool:
|
async def should_expose(self, state) -> bool:
|
||||||
"""Return if entity should be exposed."""
|
"""Return if entity should be exposed."""
|
||||||
|
|
||||||
def should_2fa(self, state):
|
def should_2fa(self, state):
|
||||||
|
@ -535,16 +535,14 @@ class GoogleEntity:
|
||||||
]
|
]
|
||||||
return self._traits
|
return self._traits
|
||||||
|
|
||||||
@callback
|
async def should_expose(self):
|
||||||
def should_expose(self):
|
|
||||||
"""If entity should be exposed."""
|
"""If entity should be exposed."""
|
||||||
return self.config.should_expose(self.state)
|
return await self.config.should_expose(self.state)
|
||||||
|
|
||||||
@callback
|
async def should_expose_local(self) -> bool:
|
||||||
def should_expose_local(self) -> bool:
|
|
||||||
"""Return if the entity should be exposed locally."""
|
"""Return if the entity should be exposed locally."""
|
||||||
return (
|
return (
|
||||||
self.should_expose()
|
await self.should_expose()
|
||||||
and get_google_type(
|
and get_google_type(
|
||||||
self.state.domain, self.state.attributes.get(ATTR_DEVICE_CLASS)
|
self.state.domain, self.state.attributes.get(ATTR_DEVICE_CLASS)
|
||||||
)
|
)
|
||||||
|
@ -587,7 +585,7 @@ class GoogleEntity:
|
||||||
trait.might_2fa(domain, features, device_class) for trait in self.traits()
|
trait.might_2fa(domain, features, device_class) for trait in self.traits()
|
||||||
)
|
)
|
||||||
|
|
||||||
def sync_serialize(self, agent_user_id, instance_uuid):
|
async def sync_serialize(self, agent_user_id, instance_uuid):
|
||||||
"""Serialize entity for a SYNC response.
|
"""Serialize entity for a SYNC response.
|
||||||
|
|
||||||
https://developers.google.com/actions/smarthome/create-app#actiondevicessync
|
https://developers.google.com/actions/smarthome/create-app#actiondevicessync
|
||||||
|
@ -623,7 +621,7 @@ class GoogleEntity:
|
||||||
device["name"]["nicknames"].extend(entity_entry.aliases)
|
device["name"]["nicknames"].extend(entity_entry.aliases)
|
||||||
|
|
||||||
# Add local SDK info if enabled
|
# Add local SDK info if enabled
|
||||||
if self.config.is_local_sdk_active and self.should_expose_local():
|
if self.config.is_local_sdk_active and await self.should_expose_local():
|
||||||
device["otherDeviceIds"] = [{"deviceId": self.entity_id}]
|
device["otherDeviceIds"] = [{"deviceId": self.entity_id}]
|
||||||
device["customData"] = {
|
device["customData"] = {
|
||||||
"webhookId": self.config.get_local_webhook_id(agent_user_id),
|
"webhookId": self.config.get_local_webhook_id(agent_user_id),
|
||||||
|
|
|
@ -111,7 +111,7 @@ class GoogleConfig(AbstractConfig):
|
||||||
"""Return if states should be proactively reported."""
|
"""Return if states should be proactively reported."""
|
||||||
return self._config.get(CONF_REPORT_STATE)
|
return self._config.get(CONF_REPORT_STATE)
|
||||||
|
|
||||||
def should_expose(self, state) -> bool:
|
async def should_expose(self, state) -> bool:
|
||||||
"""Return if entity should be exposed."""
|
"""Return if entity should be exposed."""
|
||||||
expose_by_default = self._config.get(CONF_EXPOSE_BY_DEFAULT)
|
expose_by_default = self._config.get(CONF_EXPOSE_BY_DEFAULT)
|
||||||
exposed_domains = self._config.get(CONF_EXPOSED_DOMAINS)
|
exposed_domains = self._config.get(CONF_EXPOSED_DOMAINS)
|
||||||
|
|
|
@ -63,7 +63,7 @@ def async_enable_report_state(hass: HomeAssistant, google_config: AbstractConfig
|
||||||
if not new_state:
|
if not new_state:
|
||||||
return
|
return
|
||||||
|
|
||||||
if not google_config.should_expose(new_state):
|
if not await google_config.should_expose(new_state):
|
||||||
return
|
return
|
||||||
|
|
||||||
entity = GoogleEntity(hass, google_config, new_state)
|
entity = GoogleEntity(hass, google_config, new_state)
|
||||||
|
@ -115,7 +115,7 @@ def async_enable_report_state(hass: HomeAssistant, google_config: AbstractConfig
|
||||||
checker = await create_checker(hass, DOMAIN, extra_significant_check)
|
checker = await create_checker(hass, DOMAIN, extra_significant_check)
|
||||||
|
|
||||||
for entity in async_get_entities(hass, google_config):
|
for entity in async_get_entities(hass, google_config):
|
||||||
if not entity.should_expose():
|
if not await entity.should_expose():
|
||||||
continue
|
continue
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -87,11 +87,11 @@ async def async_devices_sync_response(hass, config, agent_user_id):
|
||||||
devices = []
|
devices = []
|
||||||
|
|
||||||
for entity in entities:
|
for entity in entities:
|
||||||
if not entity.should_expose():
|
if not await entity.should_expose():
|
||||||
continue
|
continue
|
||||||
|
|
||||||
try:
|
try:
|
||||||
devices.append(entity.sync_serialize(agent_user_id, instance_uuid))
|
devices.append(await entity.sync_serialize(agent_user_id, instance_uuid))
|
||||||
except Exception: # pylint: disable=broad-except
|
except Exception: # pylint: disable=broad-except
|
||||||
_LOGGER.exception("Error serializing %s", entity.entity_id)
|
_LOGGER.exception("Error serializing %s", entity.entity_id)
|
||||||
|
|
||||||
|
@ -318,7 +318,7 @@ async def async_devices_reachable(
|
||||||
"devices": [
|
"devices": [
|
||||||
entity.reachable_device_serialize()
|
entity.reachable_device_serialize()
|
||||||
for entity in async_get_entities(hass, data.config)
|
for entity in async_get_entities(hass, data.config)
|
||||||
if entity.entity_id in google_ids and entity.should_expose_local()
|
if entity.entity_id in google_ids and await entity.should_expose_local()
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -343,7 +343,7 @@ async def async_setup(hass: ha.HomeAssistant, config: ConfigType) -> bool: # no
|
||||||
)
|
)
|
||||||
|
|
||||||
exposed_entities = ExposedEntities(hass)
|
exposed_entities = ExposedEntities(hass)
|
||||||
await exposed_entities.async_initialize()
|
await exposed_entities.async_load()
|
||||||
hass.data[DATA_EXPOSED_ENTITIES] = exposed_entities
|
hass.data[DATA_EXPOSED_ENTITIES] = exposed_entities
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
|
@ -3,6 +3,7 @@ from __future__ import annotations
|
||||||
|
|
||||||
from collections.abc import Callable, Mapping
|
from collections.abc import Callable, Mapping
|
||||||
import dataclasses
|
import dataclasses
|
||||||
|
from itertools import chain
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
@ -14,6 +15,11 @@ from homeassistant.const import CLOUD_NEVER_EXPOSED_ENTITIES
|
||||||
from homeassistant.core import HomeAssistant, callback, split_entity_id
|
from homeassistant.core import HomeAssistant, callback, split_entity_id
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.helpers import entity_registry as er
|
from homeassistant.helpers import entity_registry as er
|
||||||
|
from homeassistant.helpers.collection import (
|
||||||
|
IDManager,
|
||||||
|
SerializedStorageCollection,
|
||||||
|
StorageCollection,
|
||||||
|
)
|
||||||
from homeassistant.helpers.entity import get_device_class
|
from homeassistant.helpers.entity import get_device_class
|
||||||
from homeassistant.helpers.storage import Store
|
from homeassistant.helpers.storage import Store
|
||||||
|
|
||||||
|
@ -77,25 +83,58 @@ class AssistantPreferences:
|
||||||
return {"expose_new": self.expose_new}
|
return {"expose_new": self.expose_new}
|
||||||
|
|
||||||
|
|
||||||
class ExposedEntities:
|
@dataclasses.dataclass(frozen=True)
|
||||||
"""Control assistant settings."""
|
class ExposedEntity:
|
||||||
|
"""An exposed entity without a unique_id."""
|
||||||
|
|
||||||
|
assistants: dict[str, dict[str, Any]]
|
||||||
|
|
||||||
|
def to_json(self, entity_id: str) -> dict[str, Any]:
|
||||||
|
"""Return a JSON serializable representation for storage."""
|
||||||
|
return {
|
||||||
|
"assistants": self.assistants,
|
||||||
|
"id": entity_id,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class SerializedExposedEntities(SerializedStorageCollection):
|
||||||
|
"""Serialized exposed entities storage storage collection."""
|
||||||
|
|
||||||
|
assistants: dict[str, dict[str, Any]]
|
||||||
|
|
||||||
|
|
||||||
|
class ExposedEntitiesIDManager(IDManager):
|
||||||
|
"""ID manager for tags."""
|
||||||
|
|
||||||
|
def generate_id(self, suggestion: str) -> str:
|
||||||
|
"""Generate an ID."""
|
||||||
|
assert not self.has_id(suggestion)
|
||||||
|
return suggestion
|
||||||
|
|
||||||
|
|
||||||
|
class ExposedEntities(StorageCollection[ExposedEntity, SerializedExposedEntities]):
|
||||||
|
"""Control assistant settings.
|
||||||
|
|
||||||
|
Settings for entities without a unique_id are stored in the store.
|
||||||
|
Settings for entities with a unique_id are stored in the entity registry.
|
||||||
|
"""
|
||||||
|
|
||||||
_assistants: dict[str, AssistantPreferences]
|
_assistants: dict[str, AssistantPreferences]
|
||||||
|
|
||||||
def __init__(self, hass: HomeAssistant) -> None:
|
def __init__(self, hass: HomeAssistant) -> None:
|
||||||
"""Initialize."""
|
"""Initialize."""
|
||||||
self._hass = hass
|
super().__init__(
|
||||||
self._listeners: dict[str, list[Callable[[], None]]] = {}
|
Store(hass, STORAGE_VERSION, STORAGE_KEY), ExposedEntitiesIDManager()
|
||||||
self._store: Store[dict[str, dict[str, dict[str, Any]]]] = Store(
|
|
||||||
hass, STORAGE_VERSION, STORAGE_KEY
|
|
||||||
)
|
)
|
||||||
|
self._listeners: dict[str, list[Callable[[], None]]] = {}
|
||||||
|
|
||||||
async def async_initialize(self) -> None:
|
async def async_load(self) -> None:
|
||||||
"""Finish initializing."""
|
"""Finish initializing."""
|
||||||
websocket_api.async_register_command(self._hass, ws_expose_entity)
|
await super().async_load()
|
||||||
websocket_api.async_register_command(self._hass, ws_expose_new_entities_get)
|
websocket_api.async_register_command(self.hass, ws_expose_entity)
|
||||||
websocket_api.async_register_command(self._hass, ws_expose_new_entities_set)
|
websocket_api.async_register_command(self.hass, ws_expose_new_entities_get)
|
||||||
await self.async_load()
|
websocket_api.async_register_command(self.hass, ws_expose_new_entities_set)
|
||||||
|
websocket_api.async_register_command(self.hass, ws_list_exposed_entities)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def async_listen_entity_updates(
|
def async_listen_entity_updates(
|
||||||
|
@ -104,17 +143,18 @@ class ExposedEntities:
|
||||||
"""Listen for updates to entity expose settings."""
|
"""Listen for updates to entity expose settings."""
|
||||||
self._listeners.setdefault(assistant, []).append(listener)
|
self._listeners.setdefault(assistant, []).append(listener)
|
||||||
|
|
||||||
@callback
|
async def async_expose_entity(
|
||||||
def async_expose_entity(
|
|
||||||
self, assistant: str, entity_id: str, should_expose: bool
|
self, assistant: str, entity_id: str, should_expose: bool
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Expose an entity to an assistant.
|
"""Expose an entity to an assistant.
|
||||||
|
|
||||||
Notify listeners if expose flag was changed.
|
Notify listeners if expose flag was changed.
|
||||||
"""
|
"""
|
||||||
entity_registry = er.async_get(self._hass)
|
entity_registry = er.async_get(self.hass)
|
||||||
if not (registry_entry := entity_registry.async_get(entity_id)):
|
if not (registry_entry := entity_registry.async_get(entity_id)):
|
||||||
raise HomeAssistantError("Unknown entity")
|
return await self._async_expose_legacy_entity(
|
||||||
|
assistant, entity_id, should_expose
|
||||||
|
)
|
||||||
|
|
||||||
assistant_options: Mapping[str, Any]
|
assistant_options: Mapping[str, Any]
|
||||||
if (
|
if (
|
||||||
|
@ -129,6 +169,34 @@ class ExposedEntities:
|
||||||
for listener in self._listeners.get(assistant, []):
|
for listener in self._listeners.get(assistant, []):
|
||||||
listener()
|
listener()
|
||||||
|
|
||||||
|
async def _async_expose_legacy_entity(
|
||||||
|
self, assistant: str, entity_id: str, should_expose: bool
|
||||||
|
) -> None:
|
||||||
|
"""Expose an entity to an assistant.
|
||||||
|
|
||||||
|
Notify listeners if expose flag was changed.
|
||||||
|
"""
|
||||||
|
if (
|
||||||
|
(exposed_entity := self.data.get(entity_id))
|
||||||
|
and (assistant_options := exposed_entity.assistants.get(assistant, {}))
|
||||||
|
and assistant_options.get("should_expose") == should_expose
|
||||||
|
):
|
||||||
|
return
|
||||||
|
|
||||||
|
if exposed_entity:
|
||||||
|
await self.async_update_item(
|
||||||
|
entity_id, {"assistants": {assistant: {"should_expose": should_expose}}}
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
await self.async_create_item(
|
||||||
|
{
|
||||||
|
"entity_id": entity_id,
|
||||||
|
"assistants": {assistant: {"should_expose": should_expose}},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
for listener in self._listeners.get(assistant, []):
|
||||||
|
listener()
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def async_get_expose_new_entities(self, assistant: str) -> bool:
|
def async_get_expose_new_entities(self, assistant: str) -> bool:
|
||||||
"""Check if new entities are exposed to an assistant."""
|
"""Check if new entities are exposed to an assistant."""
|
||||||
|
@ -147,9 +215,14 @@ class ExposedEntities:
|
||||||
self, assistant: str
|
self, assistant: str
|
||||||
) -> dict[str, Mapping[str, Any]]:
|
) -> dict[str, Mapping[str, Any]]:
|
||||||
"""Get all entity expose settings for an assistant."""
|
"""Get all entity expose settings for an assistant."""
|
||||||
entity_registry = er.async_get(self._hass)
|
entity_registry = er.async_get(self.hass)
|
||||||
result: dict[str, Mapping[str, Any]] = {}
|
result: dict[str, Mapping[str, Any]] = {}
|
||||||
|
|
||||||
|
options: Mapping | None
|
||||||
|
for entity_id, exposed_entity in self.data.items():
|
||||||
|
if options := exposed_entity.assistants.get(assistant):
|
||||||
|
result[entity_id] = options
|
||||||
|
|
||||||
for entity_id, entry in entity_registry.entities.items():
|
for entity_id, entry in entity_registry.entities.items():
|
||||||
if options := entry.options.get(assistant):
|
if options := entry.options.get(assistant):
|
||||||
result[entity_id] = options
|
result[entity_id] = options
|
||||||
|
@ -159,31 +232,33 @@ class ExposedEntities:
|
||||||
@callback
|
@callback
|
||||||
def async_get_entity_settings(self, entity_id: str) -> dict[str, Mapping[str, Any]]:
|
def async_get_entity_settings(self, entity_id: str) -> dict[str, Mapping[str, Any]]:
|
||||||
"""Get assistant expose settings for an entity."""
|
"""Get assistant expose settings for an entity."""
|
||||||
entity_registry = er.async_get(self._hass)
|
entity_registry = er.async_get(self.hass)
|
||||||
result: dict[str, Mapping[str, Any]] = {}
|
result: dict[str, Mapping[str, Any]] = {}
|
||||||
|
|
||||||
if not (registry_entry := entity_registry.async_get(entity_id)):
|
assistant_settings: Mapping
|
||||||
|
if registry_entry := entity_registry.async_get(entity_id):
|
||||||
|
assistant_settings = registry_entry.options
|
||||||
|
elif exposed_entity := self.data.get(entity_id):
|
||||||
|
assistant_settings = exposed_entity.assistants
|
||||||
|
else:
|
||||||
raise HomeAssistantError("Unknown entity")
|
raise HomeAssistantError("Unknown entity")
|
||||||
|
|
||||||
for assistant in KNOWN_ASSISTANTS:
|
for assistant in KNOWN_ASSISTANTS:
|
||||||
if options := registry_entry.options.get(assistant):
|
if options := assistant_settings.get(assistant):
|
||||||
result[assistant] = options
|
result[assistant] = options
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@callback
|
async def async_should_expose(self, assistant: str, entity_id: str) -> bool:
|
||||||
def async_should_expose(self, assistant: str, entity_id: str) -> bool:
|
|
||||||
"""Return True if an entity should be exposed to an assistant."""
|
"""Return True if an entity should be exposed to an assistant."""
|
||||||
should_expose: bool
|
should_expose: bool
|
||||||
|
|
||||||
if entity_id in CLOUD_NEVER_EXPOSED_ENTITIES:
|
if entity_id in CLOUD_NEVER_EXPOSED_ENTITIES:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
entity_registry = er.async_get(self._hass)
|
entity_registry = er.async_get(self.hass)
|
||||||
if not (registry_entry := entity_registry.async_get(entity_id)):
|
if not (registry_entry := entity_registry.async_get(entity_id)):
|
||||||
# Entities which are not in the entity registry are not exposed
|
return await self._async_should_expose_legacy_entity(assistant, entity_id)
|
||||||
return False
|
|
||||||
|
|
||||||
if assistant in registry_entry.options:
|
if assistant in registry_entry.options:
|
||||||
if "should_expose" in registry_entry.options[assistant]:
|
if "should_expose" in registry_entry.options[assistant]:
|
||||||
should_expose = registry_entry.options[assistant]["should_expose"]
|
should_expose = registry_entry.options[assistant]["should_expose"]
|
||||||
|
@ -202,11 +277,43 @@ class ExposedEntities:
|
||||||
|
|
||||||
return should_expose
|
return should_expose
|
||||||
|
|
||||||
|
async def _async_should_expose_legacy_entity(
|
||||||
|
self, assistant: str, entity_id: str
|
||||||
|
) -> bool:
|
||||||
|
"""Return True if an entity should be exposed to an assistant."""
|
||||||
|
should_expose: bool
|
||||||
|
|
||||||
|
if (
|
||||||
|
exposed_entity := self.data.get(entity_id)
|
||||||
|
) and assistant in exposed_entity.assistants:
|
||||||
|
if "should_expose" in exposed_entity.assistants[assistant]:
|
||||||
|
should_expose = exposed_entity.assistants[assistant]["should_expose"]
|
||||||
|
return should_expose
|
||||||
|
|
||||||
|
if self.async_get_expose_new_entities(assistant):
|
||||||
|
should_expose = self._is_default_exposed(entity_id, None)
|
||||||
|
else:
|
||||||
|
should_expose = False
|
||||||
|
|
||||||
|
if exposed_entity:
|
||||||
|
await self.async_update_item(
|
||||||
|
entity_id, {"assistants": {assistant: {"should_expose": should_expose}}}
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
await self.async_create_item(
|
||||||
|
{
|
||||||
|
"entity_id": entity_id,
|
||||||
|
"assistants": {assistant: {"should_expose": should_expose}},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return should_expose
|
||||||
|
|
||||||
def _is_default_exposed(
|
def _is_default_exposed(
|
||||||
self, entity_id: str, registry_entry: er.RegistryEntry
|
self, entity_id: str, registry_entry: er.RegistryEntry | None
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""Return True if an entity is exposed by default."""
|
"""Return True if an entity is exposed by default."""
|
||||||
if (
|
if registry_entry and (
|
||||||
registry_entry.entity_category is not None
|
registry_entry.entity_category is not None
|
||||||
or registry_entry.hidden_by is not None
|
or registry_entry.hidden_by is not None
|
||||||
):
|
):
|
||||||
|
@ -216,7 +323,7 @@ class ExposedEntities:
|
||||||
if domain in DEFAULT_EXPOSED_DOMAINS:
|
if domain in DEFAULT_EXPOSED_DOMAINS:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
device_class = get_device_class(self._hass, entity_id)
|
device_class = get_device_class(self.hass, entity_id)
|
||||||
if (
|
if (
|
||||||
domain == "binary_sensor"
|
domain == "binary_sensor"
|
||||||
and device_class in DEFAULT_EXPOSED_BINARY_SENSOR_DEVICE_CLASSES
|
and device_class in DEFAULT_EXPOSED_BINARY_SENSOR_DEVICE_CLASSES
|
||||||
|
@ -228,37 +335,71 @@ class ExposedEntities:
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
async def async_load(self) -> None:
|
async def _process_create_data(self, data: dict) -> dict:
|
||||||
|
"""Validate the config is valid."""
|
||||||
|
return data
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def _get_suggested_id(self, info: dict) -> str:
|
||||||
|
"""Suggest an ID based on the config."""
|
||||||
|
entity_id: str = info["entity_id"]
|
||||||
|
return entity_id
|
||||||
|
|
||||||
|
async def _update_data(
|
||||||
|
self, item: ExposedEntity, update_data: dict
|
||||||
|
) -> ExposedEntity:
|
||||||
|
"""Return a new updated item."""
|
||||||
|
new_assistant_settings: dict[str, Any] = update_data["assistants"]
|
||||||
|
old_assistant_settings = item.assistants
|
||||||
|
for assistant, old_settings in old_assistant_settings.items():
|
||||||
|
new_settings = new_assistant_settings.get(assistant, {})
|
||||||
|
new_assistant_settings[assistant] = old_settings | new_settings
|
||||||
|
return dataclasses.replace(item, assistants=new_assistant_settings)
|
||||||
|
|
||||||
|
def _create_item(self, item_id: str, data: dict) -> ExposedEntity:
|
||||||
|
"""Create an item from validated config."""
|
||||||
|
del data["entity_id"]
|
||||||
|
return ExposedEntity(**data)
|
||||||
|
|
||||||
|
def _deserialize_item(self, data: dict) -> ExposedEntity:
|
||||||
|
"""Create an item from its serialized representation."""
|
||||||
|
del data["entity_id"]
|
||||||
|
return ExposedEntity(**data)
|
||||||
|
|
||||||
|
def _serialize_item(self, item_id: str, item: ExposedEntity) -> dict:
|
||||||
|
"""Return the serialized representation of an item for storing."""
|
||||||
|
return item.to_json(item_id)
|
||||||
|
|
||||||
|
async def _async_load_data(self) -> SerializedExposedEntities | None:
|
||||||
"""Load from the store."""
|
"""Load from the store."""
|
||||||
data = await self._store.async_load()
|
data = await super()._async_load_data()
|
||||||
|
|
||||||
assistants: dict[str, AssistantPreferences] = {}
|
assistants: dict[str, AssistantPreferences] = {}
|
||||||
|
|
||||||
if data:
|
if data and "assistants" in data:
|
||||||
for domain, preferences in data["assistants"].items():
|
for domain, preferences in data["assistants"].items():
|
||||||
assistants[domain] = AssistantPreferences(**preferences)
|
assistants[domain] = AssistantPreferences(**preferences)
|
||||||
|
|
||||||
self._assistants = assistants
|
self._assistants = assistants
|
||||||
|
|
||||||
@callback
|
if data and "items" not in data:
|
||||||
def _async_schedule_save(self) -> None:
|
return None # type: ignore[unreachable]
|
||||||
"""Schedule saving the preferences."""
|
|
||||||
self._store.async_delay_save(self._data_to_save, SAVE_DELAY)
|
|
||||||
|
|
||||||
@callback
|
|
||||||
def _data_to_save(self) -> dict[str, dict[str, dict[str, Any]]]:
|
|
||||||
"""Return data to store in a file."""
|
|
||||||
data = {}
|
|
||||||
|
|
||||||
data["assistants"] = {
|
|
||||||
domain: preferences.to_json()
|
|
||||||
for domain, preferences in self._assistants.items()
|
|
||||||
}
|
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def _data_to_save(self) -> SerializedExposedEntities:
|
||||||
|
"""Return JSON-compatible date for storing to file."""
|
||||||
|
base_data = super()._base_data_to_save()
|
||||||
|
return {
|
||||||
|
"items": base_data["items"],
|
||||||
|
"assistants": {
|
||||||
|
domain: preferences.to_json()
|
||||||
|
for domain, preferences in self._assistants.items()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@callback
|
|
||||||
@websocket_api.require_admin
|
@websocket_api.require_admin
|
||||||
@websocket_api.websocket_command(
|
@websocket_api.websocket_command(
|
||||||
{
|
{
|
||||||
|
@ -268,11 +409,11 @@ class ExposedEntities:
|
||||||
vol.Required("should_expose"): bool,
|
vol.Required("should_expose"): bool,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
def ws_expose_entity(
|
@websocket_api.async_response
|
||||||
|
async def ws_expose_entity(
|
||||||
hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict[str, Any]
|
hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict[str, Any]
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Expose an entity to an assistant."""
|
"""Expose an entity to an assistant."""
|
||||||
entity_registry = er.async_get(hass)
|
|
||||||
entity_ids: str = msg["entity_ids"]
|
entity_ids: str = msg["entity_ids"]
|
||||||
|
|
||||||
if blocked := next(
|
if blocked := next(
|
||||||
|
@ -288,28 +429,40 @@ def ws_expose_entity(
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
if unknown := next(
|
|
||||||
(
|
|
||||||
entity_id
|
|
||||||
for entity_id in entity_ids
|
|
||||||
if entity_id not in entity_registry.entities
|
|
||||||
),
|
|
||||||
None,
|
|
||||||
):
|
|
||||||
connection.send_error(
|
|
||||||
msg["id"], websocket_api.const.ERR_NOT_FOUND, f"can't expose '{unknown}'"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
exposed_entities: ExposedEntities = hass.data[DATA_EXPOSED_ENTITIES]
|
exposed_entities: ExposedEntities = hass.data[DATA_EXPOSED_ENTITIES]
|
||||||
for entity_id in entity_ids:
|
for entity_id in entity_ids:
|
||||||
for assistant in msg["assistants"]:
|
for assistant in msg["assistants"]:
|
||||||
exposed_entities.async_expose_entity(
|
await exposed_entities.async_expose_entity(
|
||||||
assistant, entity_id, msg["should_expose"]
|
assistant, entity_id, msg["should_expose"]
|
||||||
)
|
)
|
||||||
connection.send_result(msg["id"])
|
connection.send_result(msg["id"])
|
||||||
|
|
||||||
|
|
||||||
|
@callback
|
||||||
|
@websocket_api.require_admin
|
||||||
|
@websocket_api.websocket_command(
|
||||||
|
{
|
||||||
|
vol.Required("type"): "homeassistant/expose_entity/list",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
def ws_list_exposed_entities(
|
||||||
|
hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict[str, Any]
|
||||||
|
) -> None:
|
||||||
|
"""Expose an entity to an assistant."""
|
||||||
|
result: dict[str, Any] = {}
|
||||||
|
|
||||||
|
exposed_entities: ExposedEntities = hass.data[DATA_EXPOSED_ENTITIES]
|
||||||
|
entity_registry = er.async_get(hass)
|
||||||
|
for entity_id in chain(exposed_entities.data, entity_registry.entities):
|
||||||
|
result[entity_id] = {}
|
||||||
|
entity_settings = async_get_entity_settings(hass, entity_id)
|
||||||
|
for assistant, settings in entity_settings.items():
|
||||||
|
if "should_expose" not in settings:
|
||||||
|
continue
|
||||||
|
result[entity_id][assistant] = settings["should_expose"]
|
||||||
|
connection.send_result(msg["id"], {"exposed_entities": result})
|
||||||
|
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
@websocket_api.require_admin
|
@websocket_api.require_admin
|
||||||
@websocket_api.websocket_command(
|
@websocket_api.websocket_command(
|
||||||
|
@ -372,8 +525,7 @@ def async_get_entity_settings(
|
||||||
return exposed_entities.async_get_entity_settings(entity_id)
|
return exposed_entities.async_get_entity_settings(entity_id)
|
||||||
|
|
||||||
|
|
||||||
@callback
|
async def async_expose_entity(
|
||||||
def async_expose_entity(
|
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
assistant: str,
|
assistant: str,
|
||||||
entity_id: str,
|
entity_id: str,
|
||||||
|
@ -381,11 +533,12 @@ def async_expose_entity(
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Get assistant expose settings for an entity."""
|
"""Get assistant expose settings for an entity."""
|
||||||
exposed_entities: ExposedEntities = hass.data[DATA_EXPOSED_ENTITIES]
|
exposed_entities: ExposedEntities = hass.data[DATA_EXPOSED_ENTITIES]
|
||||||
exposed_entities.async_expose_entity(assistant, entity_id, should_expose)
|
await exposed_entities.async_expose_entity(assistant, entity_id, should_expose)
|
||||||
|
|
||||||
|
|
||||||
@callback
|
async def async_should_expose(
|
||||||
def async_should_expose(hass: HomeAssistant, assistant: str, entity_id: str) -> bool:
|
hass: HomeAssistant, assistant: str, entity_id: str
|
||||||
|
) -> bool:
|
||||||
"""Return True if an entity should be exposed to an assistant."""
|
"""Return True if an entity should be exposed to an assistant."""
|
||||||
exposed_entities: ExposedEntities = hass.data[DATA_EXPOSED_ENTITIES]
|
exposed_entities: ExposedEntities = hass.data[DATA_EXPOSED_ENTITIES]
|
||||||
return exposed_entities.async_should_expose(assistant, entity_id)
|
return await exposed_entities.async_should_expose(assistant, entity_id)
|
||||||
|
|
|
@ -138,6 +138,6 @@ async def async_remove_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
||||||
for assistant, settings in expose_settings.items():
|
for assistant, settings in expose_settings.items():
|
||||||
if (should_expose := settings.get("should_expose")) is None:
|
if (should_expose := settings.get("should_expose")) is None:
|
||||||
continue
|
continue
|
||||||
exposed_entities.async_expose_entity(
|
await exposed_entities.async_expose_entity(
|
||||||
hass, assistant, switch_entity_id, should_expose
|
hass, assistant, switch_entity_id, should_expose
|
||||||
)
|
)
|
||||||
|
|
|
@ -111,7 +111,7 @@ class BaseEntity(Entity):
|
||||||
return
|
return
|
||||||
registry.async_update_entity(self.entity_id, name=wrapped_switch.name)
|
registry.async_update_entity(self.entity_id, name=wrapped_switch.name)
|
||||||
|
|
||||||
def copy_expose_settings() -> None:
|
async def copy_expose_settings() -> None:
|
||||||
"""Copy assistant expose settings from the wrapped entity.
|
"""Copy assistant expose settings from the wrapped entity.
|
||||||
|
|
||||||
Also unexpose the wrapped entity if exposed.
|
Also unexpose the wrapped entity if exposed.
|
||||||
|
@ -122,15 +122,15 @@ class BaseEntity(Entity):
|
||||||
for assistant, settings in expose_settings.items():
|
for assistant, settings in expose_settings.items():
|
||||||
if (should_expose := settings.get("should_expose")) is None:
|
if (should_expose := settings.get("should_expose")) is None:
|
||||||
continue
|
continue
|
||||||
exposed_entities.async_expose_entity(
|
await exposed_entities.async_expose_entity(
|
||||||
self.hass, assistant, self.entity_id, should_expose
|
self.hass, assistant, self.entity_id, should_expose
|
||||||
)
|
)
|
||||||
exposed_entities.async_expose_entity(
|
await exposed_entities.async_expose_entity(
|
||||||
self.hass, assistant, self._switch_entity_id, False
|
self.hass, assistant, self._switch_entity_id, False
|
||||||
)
|
)
|
||||||
|
|
||||||
copy_custom_name(wrapped_switch)
|
copy_custom_name(wrapped_switch)
|
||||||
copy_expose_settings()
|
await copy_expose_settings()
|
||||||
|
|
||||||
|
|
||||||
class BaseToggleEntity(BaseEntity, ToggleEntity):
|
class BaseToggleEntity(BaseEntity, ToggleEntity):
|
||||||
|
|
|
@ -2450,13 +2450,18 @@ async def test_exclude_filters(hass: HomeAssistant) -> None:
|
||||||
hass.states.async_set("cover.deny", "off", {"friendly_name": "Blocked cover"})
|
hass.states.async_set("cover.deny", "off", {"friendly_name": "Blocked cover"})
|
||||||
|
|
||||||
alexa_config = MockConfig(hass)
|
alexa_config = MockConfig(hass)
|
||||||
alexa_config.should_expose = entityfilter.generate_filter(
|
filter = entityfilter.generate_filter(
|
||||||
include_domains=[],
|
include_domains=[],
|
||||||
include_entities=[],
|
include_entities=[],
|
||||||
exclude_domains=["script"],
|
exclude_domains=["script"],
|
||||||
exclude_entities=["cover.deny"],
|
exclude_entities=["cover.deny"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def mock_should_expose(entity_id):
|
||||||
|
return filter(entity_id)
|
||||||
|
|
||||||
|
alexa_config.should_expose = mock_should_expose
|
||||||
|
|
||||||
msg = await smart_home.async_handle_message(hass, alexa_config, request)
|
msg = await smart_home.async_handle_message(hass, alexa_config, request)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
@ -2481,13 +2486,18 @@ async def test_include_filters(hass: HomeAssistant) -> None:
|
||||||
hass.states.async_set("group.allow", "off", {"friendly_name": "Allowed group"})
|
hass.states.async_set("group.allow", "off", {"friendly_name": "Allowed group"})
|
||||||
|
|
||||||
alexa_config = MockConfig(hass)
|
alexa_config = MockConfig(hass)
|
||||||
alexa_config.should_expose = entityfilter.generate_filter(
|
filter = entityfilter.generate_filter(
|
||||||
include_domains=["automation", "group"],
|
include_domains=["automation", "group"],
|
||||||
include_entities=["script.deny"],
|
include_entities=["script.deny"],
|
||||||
exclude_domains=[],
|
exclude_domains=[],
|
||||||
exclude_entities=[],
|
exclude_entities=[],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def mock_should_expose(entity_id):
|
||||||
|
return filter(entity_id)
|
||||||
|
|
||||||
|
alexa_config.should_expose = mock_should_expose
|
||||||
|
|
||||||
msg = await smart_home.async_handle_message(hass, alexa_config, request)
|
msg = await smart_home.async_handle_message(hass, alexa_config, request)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
@ -2506,13 +2516,18 @@ async def test_never_exposed_entities(hass: HomeAssistant) -> None:
|
||||||
hass.states.async_set("group.allow", "off", {"friendly_name": "Allowed group"})
|
hass.states.async_set("group.allow", "off", {"friendly_name": "Allowed group"})
|
||||||
|
|
||||||
alexa_config = MockConfig(hass)
|
alexa_config = MockConfig(hass)
|
||||||
alexa_config.should_expose = entityfilter.generate_filter(
|
filter = entityfilter.generate_filter(
|
||||||
include_domains=["group"],
|
include_domains=["group"],
|
||||||
include_entities=[],
|
include_entities=[],
|
||||||
exclude_domains=[],
|
exclude_domains=[],
|
||||||
exclude_entities=[],
|
exclude_entities=[],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def mock_should_expose(entity_id):
|
||||||
|
return filter(entity_id)
|
||||||
|
|
||||||
|
alexa_config.should_expose = mock_should_expose
|
||||||
|
|
||||||
msg = await smart_home.async_handle_message(hass, alexa_config, request)
|
msg = await smart_home.async_handle_message(hass, alexa_config, request)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
|
|
@ -370,6 +370,7 @@ async def test_websocket_update_orientation_prefs(
|
||||||
hass: HomeAssistant, hass_ws_client: WebSocketGenerator, mock_camera
|
hass: HomeAssistant, hass_ws_client: WebSocketGenerator, mock_camera
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test updating camera preferences."""
|
"""Test updating camera preferences."""
|
||||||
|
await async_setup_component(hass, "homeassistant", {})
|
||||||
|
|
||||||
client = await hass_ws_client(hass)
|
client = await hass_ws_client(hass)
|
||||||
|
|
||||||
|
|
|
@ -1888,6 +1888,7 @@ async def test_failed_cast_other_url(
|
||||||
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
|
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test warning when casting from internal_url fails."""
|
"""Test warning when casting from internal_url fails."""
|
||||||
|
await async_setup_component(hass, "homeassistant", {})
|
||||||
with assert_setup_component(1, tts.DOMAIN):
|
with assert_setup_component(1, tts.DOMAIN):
|
||||||
assert await async_setup_component(
|
assert await async_setup_component(
|
||||||
hass,
|
hass,
|
||||||
|
@ -1911,6 +1912,7 @@ async def test_failed_cast_internal_url(
|
||||||
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
|
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test warning when casting from internal_url fails."""
|
"""Test warning when casting from internal_url fails."""
|
||||||
|
await async_setup_component(hass, "homeassistant", {})
|
||||||
await async_process_ha_core_config(
|
await async_process_ha_core_config(
|
||||||
hass,
|
hass,
|
||||||
{"internal_url": "http://example.local:8123"},
|
{"internal_url": "http://example.local:8123"},
|
||||||
|
@ -1939,6 +1941,7 @@ async def test_failed_cast_external_url(
|
||||||
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
|
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test warning when casting from external_url fails."""
|
"""Test warning when casting from external_url fails."""
|
||||||
|
await async_setup_component(hass, "homeassistant", {})
|
||||||
await async_process_ha_core_config(
|
await async_process_ha_core_config(
|
||||||
hass,
|
hass,
|
||||||
{"external_url": "http://example.com:8123"},
|
{"external_url": "http://example.com:8123"},
|
||||||
|
@ -1969,6 +1972,7 @@ async def test_failed_cast_tts_base_url(
|
||||||
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
|
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test warning when casting from tts.base_url fails."""
|
"""Test warning when casting from tts.base_url fails."""
|
||||||
|
await async_setup_component(hass, "homeassistant", {})
|
||||||
with assert_setup_component(1, tts.DOMAIN):
|
with assert_setup_component(1, tts.DOMAIN):
|
||||||
assert await async_setup_component(
|
assert await async_setup_component(
|
||||||
hass,
|
hass,
|
||||||
|
|
|
@ -29,6 +29,7 @@ from tests.components.recorder.common import async_wait_recording_done
|
||||||
async def test_exclude_attributes(recorder_mock: Recorder, hass: HomeAssistant) -> None:
|
async def test_exclude_attributes(recorder_mock: Recorder, hass: HomeAssistant) -> None:
|
||||||
"""Test climate registered attributes to be excluded."""
|
"""Test climate registered attributes to be excluded."""
|
||||||
now = dt_util.utcnow()
|
now = dt_util.utcnow()
|
||||||
|
await async_setup_component(hass, "homeassistant", {})
|
||||||
await async_setup_component(
|
await async_setup_component(
|
||||||
hass, climate.DOMAIN, {climate.DOMAIN: {"platform": "demo"}}
|
hass, climate.DOMAIN, {climate.DOMAIN: {"platform": "demo"}}
|
||||||
)
|
)
|
||||||
|
|
|
@ -18,7 +18,6 @@ from homeassistant.components.homeassistant.exposed_entities import (
|
||||||
)
|
)
|
||||||
from homeassistant.const import EntityCategory
|
from homeassistant.const import EntityCategory
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
|
||||||
from homeassistant.helpers import entity_registry as er
|
from homeassistant.helpers import entity_registry as er
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
|
@ -39,10 +38,10 @@ def expose_new(hass, expose_new):
|
||||||
exposed_entities.async_set_expose_new_entities("cloud.alexa", expose_new)
|
exposed_entities.async_set_expose_new_entities("cloud.alexa", expose_new)
|
||||||
|
|
||||||
|
|
||||||
def expose_entity(hass, entity_id, should_expose):
|
async def expose_entity(hass, entity_id, should_expose):
|
||||||
"""Expose an entity to Alexa."""
|
"""Expose an entity to Alexa."""
|
||||||
exposed_entities: ExposedEntities = hass.data[DATA_EXPOSED_ENTITIES]
|
exposed_entities: ExposedEntities = hass.data[DATA_EXPOSED_ENTITIES]
|
||||||
exposed_entities.async_expose_entity("cloud.alexa", entity_id, should_expose)
|
await exposed_entities.async_expose_entity("cloud.alexa", entity_id, should_expose)
|
||||||
|
|
||||||
|
|
||||||
async def test_alexa_config_expose_entity_prefs(
|
async def test_alexa_config_expose_entity_prefs(
|
||||||
|
@ -96,36 +95,35 @@ async def test_alexa_config_expose_entity_prefs(
|
||||||
alexa_report_state=False,
|
alexa_report_state=False,
|
||||||
)
|
)
|
||||||
expose_new(hass, True)
|
expose_new(hass, True)
|
||||||
expose_entity(hass, entity_entry5.entity_id, False)
|
await expose_entity(hass, entity_entry5.entity_id, False)
|
||||||
conf = alexa_config.CloudAlexaConfig(
|
conf = alexa_config.CloudAlexaConfig(
|
||||||
hass, ALEXA_SCHEMA({}), "mock-user-id", cloud_prefs, cloud_stub
|
hass, ALEXA_SCHEMA({}), "mock-user-id", cloud_prefs, cloud_stub
|
||||||
)
|
)
|
||||||
await conf.async_initialize()
|
await conf.async_initialize()
|
||||||
|
|
||||||
# can't expose an entity which is not in the entity registry
|
# an entity which is not in the entity registry can be exposed
|
||||||
with pytest.raises(HomeAssistantError):
|
await expose_entity(hass, "light.kitchen", True)
|
||||||
expose_entity(hass, "light.kitchen", True)
|
assert await conf.should_expose("light.kitchen")
|
||||||
assert not conf.should_expose("light.kitchen")
|
|
||||||
# categorized and hidden entities should not be exposed
|
# categorized and hidden entities should not be exposed
|
||||||
assert not conf.should_expose(entity_entry1.entity_id)
|
assert not await conf.should_expose(entity_entry1.entity_id)
|
||||||
assert not conf.should_expose(entity_entry2.entity_id)
|
assert not await conf.should_expose(entity_entry2.entity_id)
|
||||||
assert not conf.should_expose(entity_entry3.entity_id)
|
assert not await conf.should_expose(entity_entry3.entity_id)
|
||||||
assert not conf.should_expose(entity_entry4.entity_id)
|
assert not await conf.should_expose(entity_entry4.entity_id)
|
||||||
# this has been hidden
|
# this has been hidden
|
||||||
assert not conf.should_expose(entity_entry5.entity_id)
|
assert not await conf.should_expose(entity_entry5.entity_id)
|
||||||
# exposed by default
|
# exposed by default
|
||||||
assert conf.should_expose(entity_entry6.entity_id)
|
assert await conf.should_expose(entity_entry6.entity_id)
|
||||||
|
|
||||||
expose_entity(hass, entity_entry5.entity_id, True)
|
await expose_entity(hass, entity_entry5.entity_id, True)
|
||||||
assert conf.should_expose(entity_entry5.entity_id)
|
assert await conf.should_expose(entity_entry5.entity_id)
|
||||||
|
|
||||||
expose_entity(hass, entity_entry5.entity_id, None)
|
await expose_entity(hass, entity_entry5.entity_id, None)
|
||||||
assert not conf.should_expose(entity_entry5.entity_id)
|
assert not await conf.should_expose(entity_entry5.entity_id)
|
||||||
|
|
||||||
assert "alexa" not in hass.config.components
|
assert "alexa" not in hass.config.components
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert "alexa" in hass.config.components
|
assert "alexa" in hass.config.components
|
||||||
assert not conf.should_expose(entity_entry5.entity_id)
|
assert not await conf.should_expose(entity_entry5.entity_id)
|
||||||
|
|
||||||
|
|
||||||
async def test_alexa_config_report_state(
|
async def test_alexa_config_report_state(
|
||||||
|
@ -370,7 +368,7 @@ async def test_alexa_update_expose_trigger_sync(
|
||||||
await conf.async_initialize()
|
await conf.async_initialize()
|
||||||
|
|
||||||
with patch_sync_helper() as (to_update, to_remove):
|
with patch_sync_helper() as (to_update, to_remove):
|
||||||
expose_entity(hass, light_entry.entity_id, True)
|
await expose_entity(hass, light_entry.entity_id, True)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
async_fire_time_changed(hass, fire_all=True)
|
async_fire_time_changed(hass, fire_all=True)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
@ -380,9 +378,9 @@ async def test_alexa_update_expose_trigger_sync(
|
||||||
assert to_remove == []
|
assert to_remove == []
|
||||||
|
|
||||||
with patch_sync_helper() as (to_update, to_remove):
|
with patch_sync_helper() as (to_update, to_remove):
|
||||||
expose_entity(hass, light_entry.entity_id, False)
|
await expose_entity(hass, light_entry.entity_id, False)
|
||||||
expose_entity(hass, binary_sensor_entry.entity_id, True)
|
await expose_entity(hass, binary_sensor_entry.entity_id, True)
|
||||||
expose_entity(hass, sensor_entry.entity_id, True)
|
await expose_entity(hass, sensor_entry.entity_id, True)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
async_fire_time_changed(hass, fire_all=True)
|
async_fire_time_changed(hass, fire_all=True)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
@ -588,7 +586,7 @@ async def test_alexa_config_migrate_expose_entity_prefs(
|
||||||
alexa_report_state=False,
|
alexa_report_state=False,
|
||||||
alexa_settings_version=1,
|
alexa_settings_version=1,
|
||||||
)
|
)
|
||||||
expose_entity(hass, entity_migrated.entity_id, False)
|
await expose_entity(hass, entity_migrated.entity_id, False)
|
||||||
|
|
||||||
cloud_prefs._prefs[PREF_ALEXA_ENTITY_CONFIGS]["light.unknown"] = {
|
cloud_prefs._prefs[PREF_ALEXA_ENTITY_CONFIGS]["light.unknown"] = {
|
||||||
PREF_SHOULD_EXPOSE: True
|
PREF_SHOULD_EXPOSE: True
|
||||||
|
|
|
@ -265,13 +265,13 @@ async def test_google_config_expose_entity(
|
||||||
state = State(entity_entry.entity_id, "on")
|
state = State(entity_entry.entity_id, "on")
|
||||||
gconf = await cloud_client.get_google_config()
|
gconf = await cloud_client.get_google_config()
|
||||||
|
|
||||||
assert gconf.should_expose(state)
|
assert await gconf.should_expose(state)
|
||||||
|
|
||||||
exposed_entities.async_expose_entity(
|
await exposed_entities.async_expose_entity(
|
||||||
"cloud.google_assistant", entity_entry.entity_id, False
|
"cloud.google_assistant", entity_entry.entity_id, False
|
||||||
)
|
)
|
||||||
|
|
||||||
assert not gconf.should_expose(state)
|
assert not await gconf.should_expose(state)
|
||||||
|
|
||||||
|
|
||||||
async def test_google_config_should_2fa(
|
async def test_google_config_should_2fa(
|
||||||
|
|
|
@ -21,7 +21,6 @@ from homeassistant.components.homeassistant.exposed_entities import (
|
||||||
)
|
)
|
||||||
from homeassistant.const import EVENT_HOMEASSISTANT_STARTED, EntityCategory
|
from homeassistant.const import EVENT_HOMEASSISTANT_STARTED, EntityCategory
|
||||||
from homeassistant.core import CoreState, HomeAssistant, State
|
from homeassistant.core import CoreState, HomeAssistant, State
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
|
||||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
from homeassistant.util.dt import utcnow
|
from homeassistant.util.dt import utcnow
|
||||||
|
@ -47,10 +46,10 @@ def expose_new(hass, expose_new):
|
||||||
exposed_entities.async_set_expose_new_entities("cloud.google_assistant", expose_new)
|
exposed_entities.async_set_expose_new_entities("cloud.google_assistant", expose_new)
|
||||||
|
|
||||||
|
|
||||||
def expose_entity(hass, entity_id, should_expose):
|
async def expose_entity(hass, entity_id, should_expose):
|
||||||
"""Expose an entity to Google."""
|
"""Expose an entity to Google."""
|
||||||
exposed_entities: ExposedEntities = hass.data[DATA_EXPOSED_ENTITIES]
|
exposed_entities: ExposedEntities = hass.data[DATA_EXPOSED_ENTITIES]
|
||||||
exposed_entities.async_expose_entity(
|
await exposed_entities.async_expose_entity(
|
||||||
"cloud.google_assistant", entity_id, should_expose
|
"cloud.google_assistant", entity_id, should_expose
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -151,7 +150,7 @@ async def test_google_update_expose_trigger_sync(
|
||||||
with patch.object(config, "async_sync_entities") as mock_sync, patch.object(
|
with patch.object(config, "async_sync_entities") as mock_sync, patch.object(
|
||||||
ga_helpers, "SYNC_DELAY", 0
|
ga_helpers, "SYNC_DELAY", 0
|
||||||
):
|
):
|
||||||
expose_entity(hass, light_entry.entity_id, True)
|
await expose_entity(hass, light_entry.entity_id, True)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
async_fire_time_changed(hass, utcnow())
|
async_fire_time_changed(hass, utcnow())
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
@ -161,9 +160,9 @@ async def test_google_update_expose_trigger_sync(
|
||||||
with patch.object(config, "async_sync_entities") as mock_sync, patch.object(
|
with patch.object(config, "async_sync_entities") as mock_sync, patch.object(
|
||||||
ga_helpers, "SYNC_DELAY", 0
|
ga_helpers, "SYNC_DELAY", 0
|
||||||
):
|
):
|
||||||
expose_entity(hass, light_entry.entity_id, False)
|
await expose_entity(hass, light_entry.entity_id, False)
|
||||||
expose_entity(hass, binary_sensor_entry.entity_id, True)
|
await expose_entity(hass, binary_sensor_entry.entity_id, True)
|
||||||
expose_entity(hass, sensor_entry.entity_id, True)
|
await expose_entity(hass, sensor_entry.entity_id, True)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
async_fire_time_changed(hass, utcnow())
|
async_fire_time_changed(hass, utcnow())
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
@ -385,7 +384,7 @@ async def test_google_config_expose_entity_prefs(
|
||||||
)
|
)
|
||||||
|
|
||||||
expose_new(hass, True)
|
expose_new(hass, True)
|
||||||
expose_entity(hass, entity_entry5.entity_id, False)
|
await expose_entity(hass, entity_entry5.entity_id, False)
|
||||||
|
|
||||||
state = State("light.kitchen", "on")
|
state = State("light.kitchen", "on")
|
||||||
state_config = State(entity_entry1.entity_id, "on")
|
state_config = State(entity_entry1.entity_id, "on")
|
||||||
|
@ -395,25 +394,24 @@ async def test_google_config_expose_entity_prefs(
|
||||||
state_not_exposed = State(entity_entry5.entity_id, "on")
|
state_not_exposed = State(entity_entry5.entity_id, "on")
|
||||||
state_exposed_default = State(entity_entry6.entity_id, "on")
|
state_exposed_default = State(entity_entry6.entity_id, "on")
|
||||||
|
|
||||||
# can't expose an entity which is not in the entity registry
|
# an entity which is not in the entity registry can be exposed
|
||||||
with pytest.raises(HomeAssistantError):
|
await expose_entity(hass, "light.kitchen", True)
|
||||||
expose_entity(hass, "light.kitchen", True)
|
assert await mock_conf.should_expose(state)
|
||||||
assert not mock_conf.should_expose(state)
|
|
||||||
# categorized and hidden entities should not be exposed
|
# categorized and hidden entities should not be exposed
|
||||||
assert not mock_conf.should_expose(state_config)
|
assert not await mock_conf.should_expose(state_config)
|
||||||
assert not mock_conf.should_expose(state_diagnostic)
|
assert not await mock_conf.should_expose(state_diagnostic)
|
||||||
assert not mock_conf.should_expose(state_hidden_integration)
|
assert not await mock_conf.should_expose(state_hidden_integration)
|
||||||
assert not mock_conf.should_expose(state_hidden_user)
|
assert not await mock_conf.should_expose(state_hidden_user)
|
||||||
# this has been hidden
|
# this has been hidden
|
||||||
assert not mock_conf.should_expose(state_not_exposed)
|
assert not await mock_conf.should_expose(state_not_exposed)
|
||||||
# exposed by default
|
# exposed by default
|
||||||
assert mock_conf.should_expose(state_exposed_default)
|
assert await mock_conf.should_expose(state_exposed_default)
|
||||||
|
|
||||||
expose_entity(hass, entity_entry5.entity_id, True)
|
await expose_entity(hass, entity_entry5.entity_id, True)
|
||||||
assert mock_conf.should_expose(state_not_exposed)
|
assert await mock_conf.should_expose(state_not_exposed)
|
||||||
|
|
||||||
expose_entity(hass, entity_entry5.entity_id, None)
|
await expose_entity(hass, entity_entry5.entity_id, None)
|
||||||
assert not mock_conf.should_expose(state_not_exposed)
|
assert not await mock_conf.should_expose(state_not_exposed)
|
||||||
|
|
||||||
|
|
||||||
def test_enabled_requires_valid_sub(
|
def test_enabled_requires_valid_sub(
|
||||||
|
@ -537,7 +535,7 @@ async def test_google_config_migrate_expose_entity_prefs(
|
||||||
google_report_state=False,
|
google_report_state=False,
|
||||||
google_settings_version=1,
|
google_settings_version=1,
|
||||||
)
|
)
|
||||||
expose_entity(hass, entity_migrated.entity_id, False)
|
await expose_entity(hass, entity_migrated.entity_id, False)
|
||||||
|
|
||||||
cloud_prefs._prefs[PREF_GOOGLE_ENTITY_CONFIGS]["light.unknown"] = {
|
cloud_prefs._prefs[PREF_GOOGLE_ENTITY_CONFIGS]["light.unknown"] = {
|
||||||
PREF_SHOULD_EXPOSE: True
|
PREF_SHOULD_EXPOSE: True
|
||||||
|
|
|
@ -32,6 +32,12 @@ LIGHT_ENTITY = "light.kitchen_lights"
|
||||||
CLOSE_THRESHOLD = 10
|
CLOSE_THRESHOLD = 10
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
async def setup_homeassistant(hass: HomeAssistant):
|
||||||
|
"""Set up the homeassistant integration."""
|
||||||
|
await async_setup_component(hass, "homeassistant", {})
|
||||||
|
|
||||||
|
|
||||||
def _close_enough(actual_rgb, testing_rgb):
|
def _close_enough(actual_rgb, testing_rgb):
|
||||||
"""Validate the given RGB value is in acceptable tolerance."""
|
"""Validate the given RGB value is in acceptable tolerance."""
|
||||||
# Convert the given RGB values to hue / saturation and then back again
|
# Convert the given RGB values to hue / saturation and then back again
|
||||||
|
|
|
@ -51,7 +51,9 @@ def expose_new(hass, expose_new):
|
||||||
exposed_entities.async_set_expose_new_entities(conversation.DOMAIN, expose_new)
|
exposed_entities.async_set_expose_new_entities(conversation.DOMAIN, expose_new)
|
||||||
|
|
||||||
|
|
||||||
def expose_entity(hass, entity_id, should_expose):
|
async def expose_entity(hass, entity_id, should_expose):
|
||||||
"""Expose an entity to the default agent."""
|
"""Expose an entity to the default agent."""
|
||||||
exposed_entities: ExposedEntities = hass.data[DATA_EXPOSED_ENTITIES]
|
exposed_entities: ExposedEntities = hass.data[DATA_EXPOSED_ENTITIES]
|
||||||
exposed_entities.async_expose_entity(conversation.DOMAIN, entity_id, should_expose)
|
await exposed_entities.async_expose_entity(
|
||||||
|
conversation.DOMAIN, entity_id, should_expose
|
||||||
|
)
|
||||||
|
|
|
@ -108,7 +108,7 @@ async def test_exposed_areas(
|
||||||
hass.states.async_set(bedroom_light.entity_id, "on")
|
hass.states.async_set(bedroom_light.entity_id, "on")
|
||||||
|
|
||||||
# Hide the bedroom light
|
# Hide the bedroom light
|
||||||
expose_entity(hass, bedroom_light.entity_id, False)
|
await expose_entity(hass, bedroom_light.entity_id, False)
|
||||||
|
|
||||||
result = await conversation.async_converse(
|
result = await conversation.async_converse(
|
||||||
hass, "turn on lights in the kitchen", None, Context(), None
|
hass, "turn on lights in the kitchen", None, Context(), None
|
||||||
|
|
|
@ -680,7 +680,7 @@ async def test_http_processing_intent_entity_exposed(
|
||||||
}
|
}
|
||||||
|
|
||||||
# Unexpose the entity
|
# Unexpose the entity
|
||||||
expose_entity(hass, "light.kitchen", False)
|
await expose_entity(hass, "light.kitchen", False)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
client = await hass_client()
|
client = await hass_client()
|
||||||
|
@ -730,7 +730,7 @@ async def test_http_processing_intent_entity_exposed(
|
||||||
}
|
}
|
||||||
|
|
||||||
# Now expose the entity
|
# Now expose the entity
|
||||||
expose_entity(hass, "light.kitchen", True)
|
await expose_entity(hass, "light.kitchen", True)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
client = await hass_client()
|
client = await hass_client()
|
||||||
|
@ -845,7 +845,7 @@ async def test_http_processing_intent_conversion_not_expose_new(
|
||||||
}
|
}
|
||||||
|
|
||||||
# Expose the entity
|
# Expose the entity
|
||||||
expose_entity(hass, "light.kitchen", True)
|
await expose_entity(hass, "light.kitchen", True)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
resp = await client.post(
|
resp = await client.post(
|
||||||
|
|
|
@ -1,3 +1,14 @@
|
||||||
"""demo conftest."""
|
"""demo conftest."""
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.setup import async_setup_component
|
||||||
|
|
||||||
from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401
|
from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401
|
||||||
from tests.components.light.conftest import mock_light_profiles # noqa: F401
|
from tests.components.light.conftest import mock_light_profiles # noqa: F401
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
async def setup_homeassistant(hass: HomeAssistant):
|
||||||
|
"""Set up the homeassistant integration."""
|
||||||
|
await async_setup_component(hass, "homeassistant", {})
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
import math
|
import math
|
||||||
from unittest.mock import AsyncMock, Mock, patch
|
from unittest.mock import AsyncMock, Mock, patch
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
from homeassistant.components import emulated_kasa
|
from homeassistant.components import emulated_kasa
|
||||||
from homeassistant.components.emulated_kasa.const import (
|
from homeassistant.components.emulated_kasa.const import (
|
||||||
CONF_POWER,
|
CONF_POWER,
|
||||||
|
@ -132,6 +134,12 @@ CONFIG_SENSOR = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
async def setup_homeassistant(hass: HomeAssistant):
|
||||||
|
"""Set up the homeassistant integration."""
|
||||||
|
await async_setup_component(hass, "homeassistant", {})
|
||||||
|
|
||||||
|
|
||||||
def nested_value(ndict, *keys):
|
def nested_value(ndict, *keys):
|
||||||
"""Return a nested dict value or None if it doesn't exist."""
|
"""Return a nested dict value or None if it doesn't exist."""
|
||||||
if len(keys) == 0:
|
if len(keys) == 0:
|
||||||
|
|
|
@ -19,6 +19,7 @@ from tests.components.recorder.common import async_wait_recording_done
|
||||||
async def test_exclude_attributes(recorder_mock: Recorder, hass: HomeAssistant) -> None:
|
async def test_exclude_attributes(recorder_mock: Recorder, hass: HomeAssistant) -> None:
|
||||||
"""Test fan registered attributes to be excluded."""
|
"""Test fan registered attributes to be excluded."""
|
||||||
now = dt_util.utcnow()
|
now = dt_util.utcnow()
|
||||||
|
await async_setup_component(hass, "homeassistant", {})
|
||||||
await async_setup_component(hass, fan.DOMAIN, {fan.DOMAIN: {"platform": "demo"}})
|
await async_setup_component(hass, fan.DOMAIN, {fan.DOMAIN: {"platform": "demo"}})
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=5))
|
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=5))
|
||||||
|
|
|
@ -58,7 +58,7 @@ class MockConfig(helpers.AbstractConfig):
|
||||||
"""Get agent user ID making request."""
|
"""Get agent user ID making request."""
|
||||||
return context.user_id
|
return context.user_id
|
||||||
|
|
||||||
def should_expose(self, state):
|
async def should_expose(self, state):
|
||||||
"""Expose it all."""
|
"""Expose it all."""
|
||||||
return self._should_expose is None or self._should_expose(state)
|
return self._should_expose is None or self._should_expose(state)
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@ async def test_diagnostics(
|
||||||
await setup.async_setup_component(
|
await setup.async_setup_component(
|
||||||
hass, switch.DOMAIN, {"switch": [{"platform": "demo"}]}
|
hass, switch.DOMAIN, {"switch": [{"platform": "demo"}]}
|
||||||
)
|
)
|
||||||
|
await async_setup_component(hass, "homeassistant", {})
|
||||||
|
|
||||||
await async_setup_component(
|
await async_setup_component(
|
||||||
hass,
|
hass,
|
||||||
|
|
|
@ -49,13 +49,13 @@ async def test_google_entity_sync_serialize_with_local_sdk(hass: HomeAssistant)
|
||||||
)
|
)
|
||||||
entity = helpers.GoogleEntity(hass, config, hass.states.get("light.ceiling_lights"))
|
entity = helpers.GoogleEntity(hass, config, hass.states.get("light.ceiling_lights"))
|
||||||
|
|
||||||
serialized = entity.sync_serialize(None, "mock-uuid")
|
serialized = await entity.sync_serialize(None, "mock-uuid")
|
||||||
assert "otherDeviceIds" not in serialized
|
assert "otherDeviceIds" not in serialized
|
||||||
assert "customData" not in serialized
|
assert "customData" not in serialized
|
||||||
|
|
||||||
config.async_enable_local_sdk()
|
config.async_enable_local_sdk()
|
||||||
|
|
||||||
serialized = entity.sync_serialize("mock-user-id", "abcdef")
|
serialized = await entity.sync_serialize("mock-user-id", "abcdef")
|
||||||
assert serialized["otherDeviceIds"] == [{"deviceId": "light.ceiling_lights"}]
|
assert serialized["otherDeviceIds"] == [{"deviceId": "light.ceiling_lights"}]
|
||||||
assert serialized["customData"] == {
|
assert serialized["customData"] == {
|
||||||
"httpPort": 1234,
|
"httpPort": 1234,
|
||||||
|
@ -68,7 +68,7 @@ async def test_google_entity_sync_serialize_with_local_sdk(hass: HomeAssistant)
|
||||||
"homeassistant.components.google_assistant.helpers.get_google_type",
|
"homeassistant.components.google_assistant.helpers.get_google_type",
|
||||||
return_value=device_type,
|
return_value=device_type,
|
||||||
):
|
):
|
||||||
serialized = entity.sync_serialize(None, "mock-uuid")
|
serialized = await entity.sync_serialize(None, "mock-uuid")
|
||||||
assert "otherDeviceIds" not in serialized
|
assert "otherDeviceIds" not in serialized
|
||||||
assert "customData" not in serialized
|
assert "customData" not in serialized
|
||||||
|
|
||||||
|
|
|
@ -257,7 +257,9 @@ async def test_should_expose(hass: HomeAssistant) -> None:
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert (
|
assert (
|
||||||
config.should_expose(State(DOMAIN + ".mock", "mock", {"view": "not None"}))
|
await config.should_expose(
|
||||||
|
State(DOMAIN + ".mock", "mock", {"view": "not None"})
|
||||||
|
)
|
||||||
is False
|
is False
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -265,7 +267,10 @@ async def test_should_expose(hass: HomeAssistant) -> None:
|
||||||
# Wait for google_assistant.helpers.async_initialize.sync_google to be called
|
# Wait for google_assistant.helpers.async_initialize.sync_google to be called
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert config.should_expose(State(CLOUD_NEVER_EXPOSED_ENTITIES[0], "mock")) is False
|
assert (
|
||||||
|
await config.should_expose(State(CLOUD_NEVER_EXPOSED_ENTITIES[0], "mock"))
|
||||||
|
is False
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def test_missing_service_account(hass: HomeAssistant) -> None:
|
async def test_missing_service_account(hass: HomeAssistant) -> None:
|
||||||
|
|
|
@ -452,6 +452,7 @@ async def test_execute(
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test an execute command."""
|
"""Test an execute command."""
|
||||||
await async_setup_component(hass, "light", {"light": {"platform": "demo"}})
|
await async_setup_component(hass, "light", {"light": {"platform": "demo"}})
|
||||||
|
await async_setup_component(hass, "homeassistant", {})
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
|
@ -635,6 +636,7 @@ async def test_execute_times_out(
|
||||||
orig_execute_limit = sh.EXECUTE_LIMIT
|
orig_execute_limit = sh.EXECUTE_LIMIT
|
||||||
sh.EXECUTE_LIMIT = 0.02 # Decrease timeout to 20ms
|
sh.EXECUTE_LIMIT = 0.02 # Decrease timeout to 20ms
|
||||||
await async_setup_component(hass, "light", {"light": {"platform": "demo"}})
|
await async_setup_component(hass, "light", {"light": {"platform": "demo"}})
|
||||||
|
await async_setup_component(hass, "homeassistant", {})
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
|
@ -907,7 +909,7 @@ async def test_serialize_input_boolean(hass: HomeAssistant) -> None:
|
||||||
"""Test serializing an input boolean entity."""
|
"""Test serializing an input boolean entity."""
|
||||||
state = State("input_boolean.bla", "on")
|
state = State("input_boolean.bla", "on")
|
||||||
entity = sh.GoogleEntity(hass, BASIC_CONFIG, state)
|
entity = sh.GoogleEntity(hass, BASIC_CONFIG, state)
|
||||||
result = entity.sync_serialize(None, "mock-uuid")
|
result = await entity.sync_serialize(None, "mock-uuid")
|
||||||
assert result == {
|
assert result == {
|
||||||
"id": "input_boolean.bla",
|
"id": "input_boolean.bla",
|
||||||
"attributes": {},
|
"attributes": {},
|
||||||
|
|
|
@ -1,2 +1,13 @@
|
||||||
"""group conftest."""
|
"""group conftest."""
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.setup import async_setup_component
|
||||||
|
|
||||||
from tests.components.light.conftest import mock_light_profiles # noqa: F401
|
from tests.components.light.conftest import mock_light_profiles # noqa: F401
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
async def setup_homeassistant(hass: HomeAssistant):
|
||||||
|
"""Set up the homeassistant integration."""
|
||||||
|
await async_setup_component(hass, "homeassistant", {})
|
||||||
|
|
|
@ -3,6 +3,8 @@ from __future__ import annotations
|
||||||
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
from homeassistant.components import group
|
from homeassistant.components import group
|
||||||
from homeassistant.components.group import ATTR_AUTO, ATTR_ENTITY_ID, ATTR_ORDER
|
from homeassistant.components.group import ATTR_AUTO, ATTR_ENTITY_ID, ATTR_ORDER
|
||||||
from homeassistant.components.recorder import Recorder
|
from homeassistant.components.recorder import Recorder
|
||||||
|
@ -16,6 +18,11 @@ from tests.common import async_fire_time_changed
|
||||||
from tests.components.recorder.common import async_wait_recording_done
|
from tests.components.recorder.common import async_wait_recording_done
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
async def setup_homeassistant():
|
||||||
|
"""Override the fixture in group.conftest."""
|
||||||
|
|
||||||
|
|
||||||
async def test_exclude_attributes(recorder_mock: Recorder, hass: HomeAssistant) -> None:
|
async def test_exclude_attributes(recorder_mock: Recorder, hass: HomeAssistant) -> None:
|
||||||
"""Test number registered attributes to be excluded."""
|
"""Test number registered attributes to be excluded."""
|
||||||
now = dt_util.utcnow()
|
now = dt_util.utcnow()
|
||||||
|
|
|
@ -4,13 +4,13 @@ import pytest
|
||||||
from homeassistant.components.homeassistant.exposed_entities import (
|
from homeassistant.components.homeassistant.exposed_entities import (
|
||||||
DATA_EXPOSED_ENTITIES,
|
DATA_EXPOSED_ENTITIES,
|
||||||
ExposedEntities,
|
ExposedEntities,
|
||||||
|
ExposedEntity,
|
||||||
async_get_assistant_settings,
|
async_get_assistant_settings,
|
||||||
async_listen_entity_updates,
|
async_listen_entity_updates,
|
||||||
async_should_expose,
|
async_should_expose,
|
||||||
)
|
)
|
||||||
from homeassistant.const import CLOUD_NEVER_EXPOSED_ENTITIES, EntityCategory
|
from homeassistant.const import CLOUD_NEVER_EXPOSED_ENTITIES, EntityCategory
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
|
||||||
from homeassistant.helpers import entity_registry as er
|
from homeassistant.helpers import entity_registry as er
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
|
|
||||||
|
@ -31,7 +31,7 @@ async def test_load_preferences(hass: HomeAssistant) -> None:
|
||||||
assert list(exposed_entities._assistants) == ["test1", "test2"]
|
assert list(exposed_entities._assistants) == ["test1", "test2"]
|
||||||
|
|
||||||
exposed_entities2 = ExposedEntities(hass)
|
exposed_entities2 = ExposedEntities(hass)
|
||||||
await flush_store(exposed_entities._store)
|
await flush_store(exposed_entities.store)
|
||||||
await exposed_entities2.async_load()
|
await exposed_entities2.async_load()
|
||||||
|
|
||||||
assert exposed_entities._assistants == exposed_entities2._assistants
|
assert exposed_entities._assistants == exposed_entities2._assistants
|
||||||
|
@ -50,6 +50,9 @@ async def test_expose_entity(
|
||||||
entry1 = entity_registry.async_get_or_create("test", "test", "unique1")
|
entry1 = entity_registry.async_get_or_create("test", "test", "unique1")
|
||||||
entry2 = entity_registry.async_get_or_create("test", "test", "unique2")
|
entry2 = entity_registry.async_get_or_create("test", "test", "unique2")
|
||||||
|
|
||||||
|
exposed_entities: ExposedEntities = hass.data[DATA_EXPOSED_ENTITIES]
|
||||||
|
assert len(exposed_entities.data) == 0
|
||||||
|
|
||||||
# Set options
|
# Set options
|
||||||
await ws_client.send_json_auto_id(
|
await ws_client.send_json_auto_id(
|
||||||
{
|
{
|
||||||
|
@ -67,6 +70,7 @@ async def test_expose_entity(
|
||||||
assert entry1.options == {"cloud.alexa": {"should_expose": True}}
|
assert entry1.options == {"cloud.alexa": {"should_expose": True}}
|
||||||
entry2 = entity_registry.async_get(entry2.entity_id)
|
entry2 = entity_registry.async_get(entry2.entity_id)
|
||||||
assert entry2.options == {}
|
assert entry2.options == {}
|
||||||
|
assert len(exposed_entities.data) == 0
|
||||||
|
|
||||||
# Update options
|
# Update options
|
||||||
await ws_client.send_json_auto_id(
|
await ws_client.send_json_auto_id(
|
||||||
|
@ -91,6 +95,7 @@ async def test_expose_entity(
|
||||||
"cloud.alexa": {"should_expose": False},
|
"cloud.alexa": {"should_expose": False},
|
||||||
"cloud.google_assistant": {"should_expose": False},
|
"cloud.google_assistant": {"should_expose": False},
|
||||||
}
|
}
|
||||||
|
assert len(exposed_entities.data) == 0
|
||||||
|
|
||||||
|
|
||||||
async def test_expose_entity_unknown(
|
async def test_expose_entity_unknown(
|
||||||
|
@ -103,6 +108,7 @@ async def test_expose_entity_unknown(
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
exposed_entities: ExposedEntities = hass.data[DATA_EXPOSED_ENTITIES]
|
exposed_entities: ExposedEntities = hass.data[DATA_EXPOSED_ENTITIES]
|
||||||
|
assert len(exposed_entities.data) == 0
|
||||||
|
|
||||||
# Set options
|
# Set options
|
||||||
await ws_client.send_json_auto_id(
|
await ws_client.send_json_auto_id(
|
||||||
|
@ -115,14 +121,41 @@ async def test_expose_entity_unknown(
|
||||||
)
|
)
|
||||||
|
|
||||||
response = await ws_client.receive_json()
|
response = await ws_client.receive_json()
|
||||||
assert not response["success"]
|
assert response["success"]
|
||||||
assert response["error"] == {
|
|
||||||
"code": "not_found",
|
assert len(exposed_entities.data) == 1
|
||||||
"message": "can't expose 'test.test'",
|
assert exposed_entities.data == {
|
||||||
|
"test.test": ExposedEntity({"cloud.alexa": {"should_expose": True}})
|
||||||
}
|
}
|
||||||
|
|
||||||
with pytest.raises(HomeAssistantError):
|
# Update options
|
||||||
exposed_entities.async_expose_entity("cloud.alexa", "test.test", True)
|
await ws_client.send_json_auto_id(
|
||||||
|
{
|
||||||
|
"type": "homeassistant/expose_entity",
|
||||||
|
"assistants": ["cloud.alexa", "cloud.google_assistant"],
|
||||||
|
"entity_ids": ["test.test", "test.test2"],
|
||||||
|
"should_expose": False,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
response = await ws_client.receive_json()
|
||||||
|
assert response["success"]
|
||||||
|
|
||||||
|
assert len(exposed_entities.data) == 2
|
||||||
|
assert exposed_entities.data == {
|
||||||
|
"test.test": ExposedEntity(
|
||||||
|
{
|
||||||
|
"cloud.alexa": {"should_expose": False},
|
||||||
|
"cloud.google_assistant": {"should_expose": False},
|
||||||
|
}
|
||||||
|
),
|
||||||
|
"test.test2": ExposedEntity(
|
||||||
|
{
|
||||||
|
"cloud.alexa": {"should_expose": False},
|
||||||
|
"cloud.google_assistant": {"should_expose": False},
|
||||||
|
}
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
async def test_expose_entity_blocked(
|
async def test_expose_entity_blocked(
|
||||||
|
@ -178,7 +211,7 @@ async def test_expose_new_entities(
|
||||||
assert response["result"] == {"expose_new": False}
|
assert response["result"] == {"expose_new": False}
|
||||||
|
|
||||||
# Check if exposed - should be False
|
# Check if exposed - should be False
|
||||||
assert async_should_expose(hass, "cloud.alexa", entry1.entity_id) is False
|
assert await async_should_expose(hass, "cloud.alexa", entry1.entity_id) is False
|
||||||
|
|
||||||
# Expose new entities to Alexa
|
# Expose new entities to Alexa
|
||||||
await ws_client.send_json_auto_id(
|
await ws_client.send_json_auto_id(
|
||||||
|
@ -201,10 +234,12 @@ async def test_expose_new_entities(
|
||||||
assert response["result"] == {"expose_new": expose_new}
|
assert response["result"] == {"expose_new": expose_new}
|
||||||
|
|
||||||
# Check again if exposed - should still be False
|
# Check again if exposed - should still be False
|
||||||
assert async_should_expose(hass, "cloud.alexa", entry1.entity_id) is False
|
assert await async_should_expose(hass, "cloud.alexa", entry1.entity_id) is False
|
||||||
|
|
||||||
# Check if exposed - should be True
|
# Check if exposed - should be True
|
||||||
assert async_should_expose(hass, "cloud.alexa", entry2.entity_id) == expose_new
|
assert (
|
||||||
|
await async_should_expose(hass, "cloud.alexa", entry2.entity_id) == expose_new
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def test_listen_updates(
|
async def test_listen_updates(
|
||||||
|
@ -226,21 +261,21 @@ async def test_listen_updates(
|
||||||
entry = entity_registry.async_get_or_create("climate", "test", "unique1")
|
entry = entity_registry.async_get_or_create("climate", "test", "unique1")
|
||||||
|
|
||||||
# Call for another assistant - listener not called
|
# Call for another assistant - listener not called
|
||||||
exposed_entities.async_expose_entity(
|
await exposed_entities.async_expose_entity(
|
||||||
"cloud.google_assistant", entry.entity_id, True
|
"cloud.google_assistant", entry.entity_id, True
|
||||||
)
|
)
|
||||||
assert len(calls) == 0
|
assert len(calls) == 0
|
||||||
|
|
||||||
# Call for our assistant - listener called
|
# Call for our assistant - listener called
|
||||||
exposed_entities.async_expose_entity("cloud.alexa", entry.entity_id, True)
|
await exposed_entities.async_expose_entity("cloud.alexa", entry.entity_id, True)
|
||||||
assert len(calls) == 1
|
assert len(calls) == 1
|
||||||
|
|
||||||
# Settings not changed - listener not called
|
# Settings not changed - listener not called
|
||||||
exposed_entities.async_expose_entity("cloud.alexa", entry.entity_id, True)
|
await exposed_entities.async_expose_entity("cloud.alexa", entry.entity_id, True)
|
||||||
assert len(calls) == 1
|
assert len(calls) == 1
|
||||||
|
|
||||||
# Settings changed - listener called
|
# Settings changed - listener called
|
||||||
exposed_entities.async_expose_entity("cloud.alexa", entry.entity_id, False)
|
await exposed_entities.async_expose_entity("cloud.alexa", entry.entity_id, False)
|
||||||
assert len(calls) == 2
|
assert len(calls) == 2
|
||||||
|
|
||||||
|
|
||||||
|
@ -258,7 +293,7 @@ async def test_get_assistant_settings(
|
||||||
|
|
||||||
assert async_get_assistant_settings(hass, "cloud.alexa") == {}
|
assert async_get_assistant_settings(hass, "cloud.alexa") == {}
|
||||||
|
|
||||||
exposed_entities.async_expose_entity("cloud.alexa", entry.entity_id, True)
|
await exposed_entities.async_expose_entity("cloud.alexa", entry.entity_id, True)
|
||||||
assert async_get_assistant_settings(hass, "cloud.alexa") == {
|
assert async_get_assistant_settings(hass, "cloud.alexa") == {
|
||||||
"climate.test_unique1": {"should_expose": True}
|
"climate.test_unique1": {"should_expose": True}
|
||||||
}
|
}
|
||||||
|
@ -287,40 +322,44 @@ async def test_should_expose(
|
||||||
assert response["success"]
|
assert response["success"]
|
||||||
|
|
||||||
# Unknown entity is not exposed
|
# Unknown entity is not exposed
|
||||||
assert async_should_expose(hass, "test.test", "test.test") is False
|
assert await async_should_expose(hass, "test.test", "test.test") is False
|
||||||
|
|
||||||
# Blocked entity is not exposed
|
# Blocked entity is not exposed
|
||||||
entry_blocked = entity_registry.async_get_or_create(
|
entry_blocked = entity_registry.async_get_or_create(
|
||||||
"group", "test", "unique", suggested_object_id="all_locks"
|
"group", "test", "unique", suggested_object_id="all_locks"
|
||||||
)
|
)
|
||||||
assert entry_blocked.entity_id == CLOUD_NEVER_EXPOSED_ENTITIES[0]
|
assert entry_blocked.entity_id == CLOUD_NEVER_EXPOSED_ENTITIES[0]
|
||||||
assert async_should_expose(hass, "cloud.alexa", entry_blocked.entity_id) is False
|
assert (
|
||||||
|
await async_should_expose(hass, "cloud.alexa", entry_blocked.entity_id) is False
|
||||||
|
)
|
||||||
|
|
||||||
# Lock is exposed
|
# Lock is exposed
|
||||||
lock1 = entity_registry.async_get_or_create("lock", "test", "unique1")
|
lock1 = entity_registry.async_get_or_create("lock", "test", "unique1")
|
||||||
assert entry_blocked.entity_id == CLOUD_NEVER_EXPOSED_ENTITIES[0]
|
assert entry_blocked.entity_id == CLOUD_NEVER_EXPOSED_ENTITIES[0]
|
||||||
assert async_should_expose(hass, "cloud.alexa", lock1.entity_id) is True
|
assert await async_should_expose(hass, "cloud.alexa", lock1.entity_id) is True
|
||||||
|
|
||||||
# Hidden entity is not exposed
|
# Hidden entity is not exposed
|
||||||
lock2 = entity_registry.async_get_or_create(
|
lock2 = entity_registry.async_get_or_create(
|
||||||
"lock", "test", "unique2", hidden_by=er.RegistryEntryHider.USER
|
"lock", "test", "unique2", hidden_by=er.RegistryEntryHider.USER
|
||||||
)
|
)
|
||||||
assert entry_blocked.entity_id == CLOUD_NEVER_EXPOSED_ENTITIES[0]
|
assert entry_blocked.entity_id == CLOUD_NEVER_EXPOSED_ENTITIES[0]
|
||||||
assert async_should_expose(hass, "cloud.alexa", lock2.entity_id) is False
|
assert await async_should_expose(hass, "cloud.alexa", lock2.entity_id) is False
|
||||||
|
|
||||||
# Entity with category is not exposed
|
# Entity with category is not exposed
|
||||||
lock3 = entity_registry.async_get_or_create(
|
lock3 = entity_registry.async_get_or_create(
|
||||||
"lock", "test", "unique3", entity_category=EntityCategory.CONFIG
|
"lock", "test", "unique3", entity_category=EntityCategory.CONFIG
|
||||||
)
|
)
|
||||||
assert entry_blocked.entity_id == CLOUD_NEVER_EXPOSED_ENTITIES[0]
|
assert entry_blocked.entity_id == CLOUD_NEVER_EXPOSED_ENTITIES[0]
|
||||||
assert async_should_expose(hass, "cloud.alexa", lock3.entity_id) is False
|
assert await async_should_expose(hass, "cloud.alexa", lock3.entity_id) is False
|
||||||
|
|
||||||
# Binary sensor without device class is not exposed
|
# Binary sensor without device class is not exposed
|
||||||
binarysensor1 = entity_registry.async_get_or_create(
|
binarysensor1 = entity_registry.async_get_or_create(
|
||||||
"binary_sensor", "test", "unique1"
|
"binary_sensor", "test", "unique1"
|
||||||
)
|
)
|
||||||
assert entry_blocked.entity_id == CLOUD_NEVER_EXPOSED_ENTITIES[0]
|
assert entry_blocked.entity_id == CLOUD_NEVER_EXPOSED_ENTITIES[0]
|
||||||
assert async_should_expose(hass, "cloud.alexa", binarysensor1.entity_id) is False
|
assert (
|
||||||
|
await async_should_expose(hass, "cloud.alexa", binarysensor1.entity_id) is False
|
||||||
|
)
|
||||||
|
|
||||||
# Binary sensor with certain device class is exposed
|
# Binary sensor with certain device class is exposed
|
||||||
binarysensor2 = entity_registry.async_get_or_create(
|
binarysensor2 = entity_registry.async_get_or_create(
|
||||||
|
@ -330,12 +369,14 @@ async def test_should_expose(
|
||||||
original_device_class="door",
|
original_device_class="door",
|
||||||
)
|
)
|
||||||
assert entry_blocked.entity_id == CLOUD_NEVER_EXPOSED_ENTITIES[0]
|
assert entry_blocked.entity_id == CLOUD_NEVER_EXPOSED_ENTITIES[0]
|
||||||
assert async_should_expose(hass, "cloud.alexa", binarysensor2.entity_id) is True
|
assert (
|
||||||
|
await async_should_expose(hass, "cloud.alexa", binarysensor2.entity_id) is True
|
||||||
|
)
|
||||||
|
|
||||||
# Sensor without device class is not exposed
|
# Sensor without device class is not exposed
|
||||||
sensor1 = entity_registry.async_get_or_create("sensor", "test", "unique1")
|
sensor1 = entity_registry.async_get_or_create("sensor", "test", "unique1")
|
||||||
assert entry_blocked.entity_id == CLOUD_NEVER_EXPOSED_ENTITIES[0]
|
assert entry_blocked.entity_id == CLOUD_NEVER_EXPOSED_ENTITIES[0]
|
||||||
assert async_should_expose(hass, "cloud.alexa", sensor1.entity_id) is False
|
assert await async_should_expose(hass, "cloud.alexa", sensor1.entity_id) is False
|
||||||
|
|
||||||
# Sensor with certain device class is exposed
|
# Sensor with certain device class is exposed
|
||||||
sensor2 = entity_registry.async_get_or_create(
|
sensor2 = entity_registry.async_get_or_create(
|
||||||
|
@ -345,4 +386,58 @@ async def test_should_expose(
|
||||||
original_device_class="temperature",
|
original_device_class="temperature",
|
||||||
)
|
)
|
||||||
assert entry_blocked.entity_id == CLOUD_NEVER_EXPOSED_ENTITIES[0]
|
assert entry_blocked.entity_id == CLOUD_NEVER_EXPOSED_ENTITIES[0]
|
||||||
assert async_should_expose(hass, "cloud.alexa", sensor2.entity_id) is True
|
assert await async_should_expose(hass, "cloud.alexa", sensor2.entity_id) is True
|
||||||
|
|
||||||
|
|
||||||
|
async def test_list_exposed_entities(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
entity_registry: er.EntityRegistry,
|
||||||
|
hass_ws_client: WebSocketGenerator,
|
||||||
|
) -> None:
|
||||||
|
"""Test list exposed entities."""
|
||||||
|
ws_client = await hass_ws_client(hass)
|
||||||
|
assert await async_setup_component(hass, "homeassistant", {})
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
entry1 = entity_registry.async_get_or_create("test", "test", "unique1")
|
||||||
|
entry2 = entity_registry.async_get_or_create("test", "test", "unique2")
|
||||||
|
|
||||||
|
# Set options for registered entities
|
||||||
|
await ws_client.send_json_auto_id(
|
||||||
|
{
|
||||||
|
"type": "homeassistant/expose_entity",
|
||||||
|
"assistants": ["cloud.alexa", "cloud.google_assistant"],
|
||||||
|
"entity_ids": [entry1.entity_id, entry2.entity_id],
|
||||||
|
"should_expose": True,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
response = await ws_client.receive_json()
|
||||||
|
assert response["success"]
|
||||||
|
|
||||||
|
# Set options for entities not in the entity registry
|
||||||
|
await ws_client.send_json_auto_id(
|
||||||
|
{
|
||||||
|
"type": "homeassistant/expose_entity",
|
||||||
|
"assistants": ["cloud.alexa", "cloud.google_assistant"],
|
||||||
|
"entity_ids": [
|
||||||
|
"test.test",
|
||||||
|
"test.test2",
|
||||||
|
],
|
||||||
|
"should_expose": False,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
response = await ws_client.receive_json()
|
||||||
|
assert response["success"]
|
||||||
|
|
||||||
|
# List exposed entities
|
||||||
|
await ws_client.send_json_auto_id({"type": "homeassistant/expose_entity/list"})
|
||||||
|
response = await ws_client.receive_json()
|
||||||
|
assert response["success"]
|
||||||
|
assert response["result"] == {
|
||||||
|
"exposed_entities": {
|
||||||
|
"test.test": {"cloud.alexa": False, "cloud.google_assistant": False},
|
||||||
|
"test.test2": {"cloud.alexa": False, "cloud.google_assistant": False},
|
||||||
|
"test.test_unique1": {"cloud.alexa": True, "cloud.google_assistant": True},
|
||||||
|
"test.test_unique2": {"cloud.alexa": True, "cloud.google_assistant": True},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
|
@ -427,6 +427,7 @@ async def test_options_flow_devices(
|
||||||
demo_config_entry = MockConfigEntry(domain="domain")
|
demo_config_entry = MockConfigEntry(domain="domain")
|
||||||
demo_config_entry.add_to_hass(hass)
|
demo_config_entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
assert await async_setup_component(hass, "homeassistant", {})
|
||||||
assert await async_setup_component(hass, "demo", {"demo": {}})
|
assert await async_setup_component(hass, "demo", {"demo": {}})
|
||||||
assert await async_setup_component(hass, "homekit", {"homekit": {}})
|
assert await async_setup_component(hass, "homekit", {"homekit": {}})
|
||||||
|
|
||||||
|
|
|
@ -319,6 +319,7 @@ async def test_config_entry_with_trigger_accessory(
|
||||||
entity_registry: er.EntityRegistry,
|
entity_registry: er.EntityRegistry,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test generating diagnostics for a bridge config entry with a trigger accessory."""
|
"""Test generating diagnostics for a bridge config entry with a trigger accessory."""
|
||||||
|
assert await async_setup_component(hass, "homeassistant", {})
|
||||||
assert await async_setup_component(hass, "demo", {"demo": {}})
|
assert await async_setup_component(hass, "demo", {"demo": {}})
|
||||||
hk_driver.publish = MagicMock()
|
hk_driver.publish = MagicMock()
|
||||||
|
|
||||||
|
|
|
@ -747,6 +747,7 @@ async def test_homekit_start_with_a_device(
|
||||||
entry = MockConfigEntry(
|
entry = MockConfigEntry(
|
||||||
domain=DOMAIN, data={CONF_NAME: "mock_name", CONF_PORT: 12345}
|
domain=DOMAIN, data={CONF_NAME: "mock_name", CONF_PORT: 12345}
|
||||||
)
|
)
|
||||||
|
assert await async_setup_component(hass, "homeassistant", {})
|
||||||
assert await async_setup_component(hass, "demo", {"demo": {}})
|
assert await async_setup_component(hass, "demo", {"demo": {}})
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
|
|
@ -81,6 +81,7 @@ async def test_bridge_with_triggers(
|
||||||
an above or below additional configuration which we have no way
|
an above or below additional configuration which we have no way
|
||||||
to input, we ignore them.
|
to input, we ignore them.
|
||||||
"""
|
"""
|
||||||
|
assert await async_setup_component(hass, "homeassistant", {})
|
||||||
assert await async_setup_component(hass, "demo", {"demo": {}})
|
assert await async_setup_component(hass, "demo", {"demo": {}})
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,7 @@ async def test_programmable_switch_button_fires_on_trigger(
|
||||||
|
|
||||||
demo_config_entry = MockConfigEntry(domain="domain")
|
demo_config_entry = MockConfigEntry(domain="domain")
|
||||||
demo_config_entry.add_to_hass(hass)
|
demo_config_entry.add_to_hass(hass)
|
||||||
|
assert await async_setup_component(hass, "homeassistant", {})
|
||||||
assert await async_setup_component(hass, "demo", {"demo": {}})
|
assert await async_setup_component(hass, "demo", {"demo": {}})
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
hass.states.async_set("light.ceiling_lights", STATE_OFF)
|
hass.states.async_set("light.ceiling_lights", STATE_OFF)
|
||||||
|
|
|
@ -26,6 +26,7 @@ from tests.components.recorder.common import async_wait_recording_done
|
||||||
async def test_exclude_attributes(recorder_mock: Recorder, hass: HomeAssistant) -> None:
|
async def test_exclude_attributes(recorder_mock: Recorder, hass: HomeAssistant) -> None:
|
||||||
"""Test light registered attributes to be excluded."""
|
"""Test light registered attributes to be excluded."""
|
||||||
now = dt_util.utcnow()
|
now = dt_util.utcnow()
|
||||||
|
assert await async_setup_component(hass, "homeassistant", {})
|
||||||
await async_setup_component(
|
await async_setup_component(
|
||||||
hass, light.DOMAIN, {light.DOMAIN: {"platform": "demo"}}
|
hass, light.DOMAIN, {light.DOMAIN: {"platform": "demo"}}
|
||||||
)
|
)
|
||||||
|
|
|
@ -18,6 +18,7 @@ from tests.components.recorder.common import async_wait_recording_done
|
||||||
|
|
||||||
async def test_exclude_attributes(recorder_mock: Recorder, hass: HomeAssistant) -> None:
|
async def test_exclude_attributes(recorder_mock: Recorder, hass: HomeAssistant) -> None:
|
||||||
"""Test number registered attributes to be excluded."""
|
"""Test number registered attributes to be excluded."""
|
||||||
|
assert await async_setup_component(hass, "homeassistant", {})
|
||||||
await async_setup_component(
|
await async_setup_component(
|
||||||
hass, number.DOMAIN, {number.DOMAIN: {"platform": "demo"}}
|
hass, number.DOMAIN, {number.DOMAIN: {"platform": "demo"}}
|
||||||
)
|
)
|
||||||
|
|
|
@ -19,6 +19,7 @@ from tests.components.recorder.common import async_wait_recording_done
|
||||||
async def test_exclude_attributes(recorder_mock: Recorder, hass: HomeAssistant) -> None:
|
async def test_exclude_attributes(recorder_mock: Recorder, hass: HomeAssistant) -> None:
|
||||||
"""Test select registered attributes to be excluded."""
|
"""Test select registered attributes to be excluded."""
|
||||||
now = dt_util.utcnow()
|
now = dt_util.utcnow()
|
||||||
|
assert await async_setup_component(hass, "homeassistant", {})
|
||||||
await async_setup_component(
|
await async_setup_component(
|
||||||
hass, select.DOMAIN, {select.DOMAIN: {"platform": "demo"}}
|
hass, select.DOMAIN, {select.DOMAIN: {"platform": "demo"}}
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
"""The tests for the Light Switch platform."""
|
"""The tests for the Light Switch platform."""
|
||||||
|
import pytest
|
||||||
|
|
||||||
from homeassistant.components.light import (
|
from homeassistant.components.light import (
|
||||||
ATTR_COLOR_MODE,
|
ATTR_COLOR_MODE,
|
||||||
ATTR_SUPPORTED_COLOR_MODES,
|
ATTR_SUPPORTED_COLOR_MODES,
|
||||||
|
@ -12,6 +14,12 @@ from . import common as switch_common
|
||||||
from tests.components.light import common
|
from tests.components.light import common
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
async def setup_homeassistant(hass: HomeAssistant):
|
||||||
|
"""Set up the homeassistant integration."""
|
||||||
|
await async_setup_component(hass, "homeassistant", {})
|
||||||
|
|
||||||
|
|
||||||
async def test_default_state(hass: HomeAssistant) -> None:
|
async def test_default_state(hass: HomeAssistant) -> None:
|
||||||
"""Test light switch default state."""
|
"""Test light switch default state."""
|
||||||
await async_setup_component(
|
await async_setup_component(
|
||||||
|
|
|
@ -6,6 +6,15 @@ from unittest.mock import AsyncMock, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.setup import async_setup_component
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
async def setup_homeassistant(hass: HomeAssistant):
|
||||||
|
"""Set up the homeassistant integration."""
|
||||||
|
await async_setup_component(hass, "homeassistant", {})
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def mock_setup_entry() -> Generator[AsyncMock, None, None]:
|
def mock_setup_entry() -> Generator[AsyncMock, None, None]:
|
||||||
|
|
|
@ -702,7 +702,7 @@ async def test_import_expose_settings_1(
|
||||||
original_name="ABC",
|
original_name="ABC",
|
||||||
)
|
)
|
||||||
for assistant, should_expose in EXPOSE_SETTINGS.items():
|
for assistant, should_expose in EXPOSE_SETTINGS.items():
|
||||||
exposed_entities.async_expose_entity(
|
await exposed_entities.async_expose_entity(
|
||||||
hass, assistant, switch_entity_entry.entity_id, should_expose
|
hass, assistant, switch_entity_entry.entity_id, should_expose
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -760,7 +760,7 @@ async def test_import_expose_settings_2(
|
||||||
original_name="ABC",
|
original_name="ABC",
|
||||||
)
|
)
|
||||||
for assistant, should_expose in EXPOSE_SETTINGS.items():
|
for assistant, should_expose in EXPOSE_SETTINGS.items():
|
||||||
exposed_entities.async_expose_entity(
|
await exposed_entities.async_expose_entity(
|
||||||
hass, assistant, switch_entity_entry.entity_id, should_expose
|
hass, assistant, switch_entity_entry.entity_id, should_expose
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -785,7 +785,7 @@ async def test_import_expose_settings_2(
|
||||||
suggested_object_id="abc",
|
suggested_object_id="abc",
|
||||||
)
|
)
|
||||||
for assistant, should_expose in EXPOSE_SETTINGS.items():
|
for assistant, should_expose in EXPOSE_SETTINGS.items():
|
||||||
exposed_entities.async_expose_entity(
|
await exposed_entities.async_expose_entity(
|
||||||
hass, assistant, switch_as_x_entity_entry.entity_id, not should_expose
|
hass, assistant, switch_as_x_entity_entry.entity_id, not should_expose
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -850,7 +850,7 @@ async def test_restore_expose_settings(
|
||||||
suggested_object_id="abc",
|
suggested_object_id="abc",
|
||||||
)
|
)
|
||||||
for assistant, should_expose in EXPOSE_SETTINGS.items():
|
for assistant, should_expose in EXPOSE_SETTINGS.items():
|
||||||
exposed_entities.async_expose_entity(
|
await exposed_entities.async_expose_entity(
|
||||||
hass, assistant, switch_as_x_entity_entry.entity_id, should_expose
|
hass, assistant, switch_as_x_entity_entry.entity_id, should_expose
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ from tests.components.recorder.common import async_wait_recording_done
|
||||||
async def test_exclude_attributes(recorder_mock: Recorder, hass: HomeAssistant) -> None:
|
async def test_exclude_attributes(recorder_mock: Recorder, hass: HomeAssistant) -> None:
|
||||||
"""Test siren registered attributes to be excluded."""
|
"""Test siren registered attributes to be excluded."""
|
||||||
now = dt_util.utcnow()
|
now = dt_util.utcnow()
|
||||||
|
assert await async_setup_component(hass, "homeassistant", {})
|
||||||
await async_setup_component(hass, text.DOMAIN, {text.DOMAIN: {"platform": "demo"}})
|
await async_setup_component(hass, text.DOMAIN, {text.DOMAIN: {"platform": "demo"}})
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=5))
|
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=5))
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue