186 files

This commit is contained in:
Home Assistant Version Control
2026-09-14 12:56:59 +00:00
parent 6be1e92941
commit bd8280defe
186 changed files with 14904 additions and 1087 deletions
+37 -3
View File
@@ -8,6 +8,7 @@ https://community.home-assistant.io/t/echo-devices-alexa-as-media-player-testers
"""
import asyncio
import contextlib
from datetime import datetime, timedelta
from json import JSONDecodeError, loads
import logging
@@ -92,6 +93,7 @@ from .const import (
LAST_CALLED_SUCCESS_PACE_S,
LAST_PING_MAX_AGE_SECONDS,
LAST_PUSH_INACTIVITY_SECONDS,
LOGIN_MAX_WAIT_S,
MIN_TIME_BETWEEN_FORCED_SCANS,
MIN_TIME_BETWEEN_SCANS,
NOTIFICATION_COOLDOWN,
@@ -691,6 +693,14 @@ async def async_setup_entry(hass, config_entry):
hass.data[DATA_ALEXAMEDIA].setdefault("accounts", {})
hass.data[DATA_ALEXAMEDIA].setdefault("config_flows", {})
hass.data[DATA_ALEXAMEDIA].setdefault("notify_service", None)
# The collector is removed when the last account unloads, so recreating it
# here re-arms boot tracking at the domain lifecycle boundary: a reload
# measures its own setup instead of the async_setup of this Home Assistant
# run. Tracking is deliberately not re-armed for an additional account,
# which would discard the stages already recorded for the loaded ones.
if not isinstance(hass.data[DATA_ALEXAMEDIA].get("metrics"), AlexaMetrics):
hass.data[DATA_ALEXAMEDIA]["metrics"] = AlexaMetrics(hass)
hass.data[DATA_ALEXAMEDIA]["metrics"].start_boot_tracking()
account = config_entry.data
email = account.get(CONF_EMAIL)
password = account.get(CONF_PASSWORD)
@@ -824,7 +834,22 @@ async def async_setup_entry(hass, config_entry):
except (JSONDecodeError, ValueError, aiohttp.ClientError) as ex:
_LOGGER.debug("[BOOT] Bootstrap cookie auth check failed: %s", ex)
if not cookie_login_ok:
await login.login(cookies=cookies)
try:
async with async_timeout.timeout(LOGIN_MAX_WAIT_S):
await login.login(cookies=cookies)
except asyncio.TimeoutError as err:
# An interrupted login can leave partial request state on the
# client, which alexapy resumes from on the next attempt. Both
# hass.data and runtime_data would hand that same client to the
# retry, so drop it and let the retry build a fresh one; the
# saved cookie is left in place.
with contextlib.suppress(Exception):
await login.close()
hass.data[DATA_ALEXAMEDIA]["accounts"][email].pop("login_obj", None)
config_entry.runtime_data = None
raise ConfigEntryNotReady(
f"Login did not complete within {LOGIN_MAX_WAIT_S:.0f}s"
) from err
_LOGGER.debug("[BOOT] login completed in %.2fs", time.monotonic() - _t)
_t = time.monotonic()
if await test_login_status(hass, config_entry, login):
@@ -973,8 +998,12 @@ async def setup_alexa(hass, config_entry, login_obj: AlexaLogin):
for binary_sensor in hass.data[DATA_ALEXAMEDIA]["accounts"][email][
"entities"
].get("binary_sensor", []):
if binary_sensor.enabled:
entities_to_monitor.add(binary_sensor.alexa_entity_id)
# AmazonKidsSensor is stored alongside the coordinator-backed
# AlexaContact sensors but is not derived from an Alexa entity: it
# polls the Echo on its own interval and has no alexa_entity_id.
alexa_entity_id = getattr(binary_sensor, "alexa_entity_id", None)
if alexa_entity_id and binary_sensor.enabled:
entities_to_monitor.add(alexa_entity_id)
for guard in (
hass.data[DATA_ALEXAMEDIA]["accounts"][email]["entities"]
@@ -3123,6 +3152,11 @@ async def async_unload_entry(hass, entry) -> bool:
if alexa_services:
await alexa_services.unregister()
hass.data[DATA_ALEXAMEDIA].pop("services")
# Per-run collectors recreated by the next setup. Leaving them behind
# keeps DATA_ALEXAMEDIA truthy, so the data structure below is never
# removed and stale boot metrics survive into the next setup.
hass.data[DATA_ALEXAMEDIA].pop("metrics", None)
hass.data[DATA_ALEXAMEDIA].pop("notify_service", None)
if hass.data[DATA_ALEXAMEDIA].get("config_flows") == {}:
_LOGGER.debug("Removing config_flows data")
async_dismiss_persistent_notification(