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(
@@ -202,7 +202,6 @@ class AmazonKidsSensor(BinarySensorEntity):
"""Attach to the Echo device."""
return {
"identifiers": {(DATA_ALEXAMEDIA, self._client.unique_id)},
"via_device": (DATA_ALEXAMEDIA, self._client.unique_id),
}
async def async_added_to_hass(self):
+3
View File
@@ -99,6 +99,9 @@ LAST_CALLED_429_BACKOFF_MAX_S = 15 * 60.0
LAST_CALLED_CONN_BACKOFF_S = 10.0
LAST_CALLED_LOGIN_BACKOFF_S = 30.0
# Deadlock guard for AlexaLogin.login() during config entry setup.
LOGIN_MAX_WAIT_S = 600.0
# Tuning constants for the per-account last-called probe worker
LAST_CALLED_DEBOUNCE_S = 3.5 # coalesce bursty pushes, but stay snappy
LAST_CALLED_RETRY_DELAY_S = 4.0 # wider retry cadence for delayed routine history
+2 -2
View File
@@ -9,10 +9,10 @@
"issue_tracker": "https://github.com/alandtse/alexa_media_player/issues",
"loggers": ["alexapy", "authcaptureproxy"],
"requirements": [
"alexapy==1.30.0",
"alexapy==1.30.1",
"packaging>=20.3",
"wrapt>=1.14.0",
"dictor>=0.1.12,<0.2"
],
"version": "5.16.0"
"version": "5.16.1"
}
-1
View File
@@ -315,7 +315,6 @@ class AlexaMediaSwitch(SwitchDevice, AlexaMedia):
"""Return device_info for device registry."""
return {
"identifiers": {(ALEXA_DOMAIN, self._client.unique_id)},
"via_device": (ALEXA_DOMAIN, self._client.unique_id),
}
@property