Remove entities from Alexa when disabling Alexa (#73999)
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
This commit is contained in:
parent
824de2ef4c
commit
6eeb1855ff
3 changed files with 78 additions and 31 deletions
|
@ -1,5 +1,8 @@
|
|||
"""Alexa configuration for Home Assistant Cloud."""
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from collections.abc import Callable
|
||||
from contextlib import suppress
|
||||
from datetime import timedelta
|
||||
from http import HTTPStatus
|
||||
|
@ -24,7 +27,15 @@ from homeassistant.helpers.event import async_call_later
|
|||
from homeassistant.setup import async_setup_component
|
||||
from homeassistant.util.dt import utcnow
|
||||
|
||||
from .const import CONF_ENTITY_CONFIG, CONF_FILTER, PREF_SHOULD_EXPOSE
|
||||
from .const import (
|
||||
CONF_ENTITY_CONFIG,
|
||||
CONF_FILTER,
|
||||
PREF_ALEXA_DEFAULT_EXPOSE,
|
||||
PREF_ALEXA_ENTITY_CONFIGS,
|
||||
PREF_ALEXA_REPORT_STATE,
|
||||
PREF_ENABLE_ALEXA,
|
||||
PREF_SHOULD_EXPOSE,
|
||||
)
|
||||
from .prefs import CloudPreferences
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
@ -54,8 +65,7 @@ class CloudAlexaConfig(alexa_config.AbstractConfig):
|
|||
self._token = None
|
||||
self._token_valid = None
|
||||
self._cur_entity_prefs = prefs.alexa_entity_configs
|
||||
self._cur_default_expose = prefs.alexa_default_expose
|
||||
self._alexa_sync_unsub = None
|
||||
self._alexa_sync_unsub: Callable[[], None] | None = None
|
||||
self._endpoint = None
|
||||
|
||||
@property
|
||||
|
@ -75,7 +85,11 @@ class CloudAlexaConfig(alexa_config.AbstractConfig):
|
|||
@property
|
||||
def should_report_state(self):
|
||||
"""Return if states should be proactively reported."""
|
||||
return self._prefs.alexa_report_state and self.authorized
|
||||
return (
|
||||
self._prefs.alexa_enabled
|
||||
and self._prefs.alexa_report_state
|
||||
and self.authorized
|
||||
)
|
||||
|
||||
@property
|
||||
def endpoint(self):
|
||||
|
@ -179,7 +193,7 @@ class CloudAlexaConfig(alexa_config.AbstractConfig):
|
|||
self._token_valid = utcnow() + timedelta(seconds=body["expires_in"])
|
||||
return self._token
|
||||
|
||||
async def _async_prefs_updated(self, prefs):
|
||||
async def _async_prefs_updated(self, prefs: CloudPreferences) -> None:
|
||||
"""Handle updated preferences."""
|
||||
if not self._cloud.is_logged_in:
|
||||
if self.is_reporting_states:
|
||||
|
@ -190,6 +204,8 @@ class CloudAlexaConfig(alexa_config.AbstractConfig):
|
|||
self._alexa_sync_unsub = None
|
||||
return
|
||||
|
||||
updated_prefs = prefs.last_updated
|
||||
|
||||
if (
|
||||
ALEXA_DOMAIN not in self.hass.config.components
|
||||
and self.enabled
|
||||
|
@ -211,28 +227,30 @@ class CloudAlexaConfig(alexa_config.AbstractConfig):
|
|||
await self.async_sync_entities()
|
||||
return
|
||||
|
||||
# If user has filter in config.yaml, don't sync.
|
||||
if not self._config[CONF_FILTER].empty_filter:
|
||||
return
|
||||
|
||||
# If entity prefs are the same, don't sync.
|
||||
if (
|
||||
self._cur_entity_prefs is prefs.alexa_entity_configs
|
||||
and self._cur_default_expose is prefs.alexa_default_expose
|
||||
# Nothing to do if no Alexa related things have changed
|
||||
if not any(
|
||||
key in updated_prefs
|
||||
for key in (
|
||||
PREF_ALEXA_DEFAULT_EXPOSE,
|
||||
PREF_ALEXA_ENTITY_CONFIGS,
|
||||
PREF_ALEXA_REPORT_STATE,
|
||||
PREF_ENABLE_ALEXA,
|
||||
)
|
||||
):
|
||||
return
|
||||
|
||||
if self._alexa_sync_unsub:
|
||||
self._alexa_sync_unsub()
|
||||
self._alexa_sync_unsub = None
|
||||
# If we update just entity preferences, delay updating
|
||||
# as we might update more
|
||||
if updated_prefs == {PREF_ALEXA_ENTITY_CONFIGS}:
|
||||
if self._alexa_sync_unsub:
|
||||
self._alexa_sync_unsub()
|
||||
|
||||
if self._cur_default_expose is not prefs.alexa_default_expose:
|
||||
await self.async_sync_entities()
|
||||
self._alexa_sync_unsub = async_call_later(
|
||||
self.hass, SYNC_DELAY, self._sync_prefs
|
||||
)
|
||||
return
|
||||
|
||||
self._alexa_sync_unsub = async_call_later(
|
||||
self.hass, SYNC_DELAY, self._sync_prefs
|
||||
)
|
||||
await self.async_sync_entities()
|
||||
|
||||
async def _sync_prefs(self, _now):
|
||||
"""Sync the updated preferences to Alexa."""
|
||||
|
@ -243,9 +261,14 @@ class CloudAlexaConfig(alexa_config.AbstractConfig):
|
|||
seen = set()
|
||||
to_update = []
|
||||
to_remove = []
|
||||
is_enabled = self.enabled
|
||||
|
||||
for entity_id, info in old_prefs.items():
|
||||
seen.add(entity_id)
|
||||
|
||||
if not is_enabled:
|
||||
to_remove.append(entity_id)
|
||||
|
||||
old_expose = info.get(PREF_SHOULD_EXPOSE)
|
||||
|
||||
if entity_id in new_prefs:
|
||||
|
@ -291,8 +314,10 @@ class CloudAlexaConfig(alexa_config.AbstractConfig):
|
|||
to_update = []
|
||||
to_remove = []
|
||||
|
||||
is_enabled = self.enabled
|
||||
|
||||
for entity in alexa_entities.async_get_entities(self.hass, self):
|
||||
if self.should_expose(entity.entity_id):
|
||||
if is_enabled and self.should_expose(entity.entity_id):
|
||||
to_update.append(entity.entity_id)
|
||||
else:
|
||||
to_remove.append(entity.entity_id)
|
||||
|
|
|
@ -50,6 +50,7 @@ class CloudPreferences:
|
|||
self._store = Store(hass, STORAGE_VERSION, STORAGE_KEY)
|
||||
self._prefs = None
|
||||
self._listeners = []
|
||||
self.last_updated: set[str] = set()
|
||||
|
||||
async def async_initialize(self):
|
||||
"""Finish initializing the preferences."""
|
||||
|
@ -308,6 +309,9 @@ class CloudPreferences:
|
|||
|
||||
async def _save_prefs(self, prefs):
|
||||
"""Save preferences to disk."""
|
||||
self.last_updated = {
|
||||
key for key, value in prefs.items() if value != self._prefs.get(key)
|
||||
}
|
||||
self._prefs = prefs
|
||||
await self._store.async_save(self._prefs)
|
||||
|
||||
|
|
|
@ -9,7 +9,6 @@ from homeassistant.components.cloud import ALEXA_SCHEMA, alexa_config
|
|||
from homeassistant.helpers import entity_registry as er
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.entity import EntityCategory
|
||||
from homeassistant.util.dt import utcnow
|
||||
|
||||
from tests.common import async_fire_time_changed, mock_registry
|
||||
|
||||
|
@ -270,10 +269,7 @@ async def test_alexa_config_fail_refresh_token(
|
|||
|
||||
@contextlib.contextmanager
|
||||
def patch_sync_helper():
|
||||
"""Patch sync helper.
|
||||
|
||||
In Py3.7 this would have been an async context manager.
|
||||
"""
|
||||
"""Patch sync helper."""
|
||||
to_update = []
|
||||
to_remove = []
|
||||
|
||||
|
@ -291,21 +287,32 @@ def patch_sync_helper():
|
|||
|
||||
async def test_alexa_update_expose_trigger_sync(hass, cloud_prefs, cloud_stub):
|
||||
"""Test Alexa config responds to updating exposed entities."""
|
||||
hass.states.async_set("binary_sensor.door", "on")
|
||||
hass.states.async_set(
|
||||
"sensor.temp",
|
||||
"23",
|
||||
{"device_class": "temperature", "unit_of_measurement": "°C"},
|
||||
)
|
||||
hass.states.async_set("light.kitchen", "off")
|
||||
|
||||
await cloud_prefs.async_update(
|
||||
alexa_enabled=True,
|
||||
alexa_report_state=False,
|
||||
)
|
||||
await alexa_config.CloudAlexaConfig(
|
||||
conf = alexa_config.CloudAlexaConfig(
|
||||
hass, ALEXA_SCHEMA({}), "mock-user-id", cloud_prefs, cloud_stub
|
||||
).async_initialize()
|
||||
)
|
||||
await conf.async_initialize()
|
||||
|
||||
with patch_sync_helper() as (to_update, to_remove):
|
||||
await cloud_prefs.async_update_alexa_entity_config(
|
||||
entity_id="light.kitchen", should_expose=True
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
async_fire_time_changed(hass, utcnow())
|
||||
async_fire_time_changed(hass, fire_all=True)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert conf._alexa_sync_unsub is None
|
||||
assert to_update == ["light.kitchen"]
|
||||
assert to_remove == []
|
||||
|
||||
|
@ -320,12 +327,23 @@ async def test_alexa_update_expose_trigger_sync(hass, cloud_prefs, cloud_stub):
|
|||
entity_id="sensor.temp", should_expose=True
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
async_fire_time_changed(hass, utcnow())
|
||||
async_fire_time_changed(hass, fire_all=True)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert conf._alexa_sync_unsub is None
|
||||
assert sorted(to_update) == ["binary_sensor.door", "sensor.temp"]
|
||||
assert to_remove == ["light.kitchen"]
|
||||
|
||||
with patch_sync_helper() as (to_update, to_remove):
|
||||
await cloud_prefs.async_update(
|
||||
alexa_enabled=False,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert conf._alexa_sync_unsub is None
|
||||
assert to_update == []
|
||||
assert to_remove == ["binary_sensor.door", "sensor.temp", "light.kitchen"]
|
||||
|
||||
|
||||
async def test_alexa_entity_registry_sync(hass, mock_cloud_login, cloud_prefs):
|
||||
"""Test Alexa config responds to entity registry."""
|
||||
|
|
Loading…
Add table
Reference in a new issue