This commit is contained in:
Home Assistant Version Control committed 2026-10-12 22:32:16 +00:00
1 parent b12fb1db7c
commit 957b247d4a
8 files changed
+134 -61

No files matched your search

+35 -17
View File
@@ -46,7 +46,7 @@ from homeassistant.const import (
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.data_entry_flow import UnknownFlow
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers import config_validation as cv, device_registry as dr
from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
@@ -70,7 +70,6 @@ from .const import (
CONF_QUEUE_DELAY,
CONF_SCAN_INTERVAL,
DATA_ALEXAMEDIA,
DATA_LISTENER,
DEFAULT_EXTENDED_ENTITY_DISCOVERY,
DEFAULT_PUBLIC_URL,
DEFAULT_QUEUE_DELAY,
@@ -744,26 +743,23 @@ async def async_setup_entry(hass, config_entry):
"notifications_pending": set(), # doppler serials that need a refresh
"notifications_refresh_task": None, # running task or None
"notifications_retry_count": 0, # simple backoff counter
"options": {
},
)
# Refreshed on every attempt: the account dict survives setup retries and
# a failed setup, so options saved in the meantime would otherwise be lost.
hass.data[DATA_ALEXAMEDIA]["accounts"][email]["options"] = {
CONF_INCLUDE_DEVICES: config_entry.data.get(CONF_INCLUDE_DEVICES, ""),
CONF_EXCLUDE_DEVICES: config_entry.data.get(CONF_EXCLUDE_DEVICES, ""),
CONF_QUEUE_DELAY: config_entry.data.get(
CONF_QUEUE_DELAY, DEFAULT_QUEUE_DELAY
),
CONF_QUEUE_DELAY: config_entry.data.get(CONF_QUEUE_DELAY, DEFAULT_QUEUE_DELAY),
CONF_SCAN_INTERVAL: config_entry.data.get(
CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL
),
CONF_PUBLIC_URL: config_entry.data.get(
CONF_PUBLIC_URL, DEFAULT_PUBLIC_URL
),
CONF_PUBLIC_URL: config_entry.data.get(CONF_PUBLIC_URL, DEFAULT_PUBLIC_URL),
CONF_EXTENDED_ENTITY_DISCOVERY: config_entry.data.get(
CONF_EXTENDED_ENTITY_DISCOVERY, DEFAULT_EXTENDED_ENTITY_DISCOVERY
),
CONF_DEBUG: config_entry.data.get(CONF_DEBUG, False),
},
DATA_LISTENER: [config_entry.add_update_listener(update_listener)],
},
)
}
uuid_dict = await calculate_uuid(hass, email, url)
uuid = uuid_dict["uuid"]
hass.data[DATA_ALEXAMEDIA]["accounts"][email]["second_account_index"] = uuid_dict[
@@ -797,8 +793,6 @@ async def async_setup_entry(hass, config_entry):
if not hass.data[DATA_ALEXAMEDIA]["accounts"][email]["second_account_index"]:
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, close_alexa_media)
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STARTED, complete_startup)
hass.bus.async_listen("alexa_media_relogin_required", relogin)
hass.bus.async_listen("alexa_media_relogin_success", login_success)
try:
_t = time.monotonic()
cookies = await login.load_cookie()
@@ -837,6 +831,16 @@ async def async_setup_entry(hass, config_entry):
try:
async with async_timeout.timeout(LOGIN_MAX_WAIT_S):
await login.login(cookies=cookies)
except (RuntimeError, ValueError) as err:
if "Session is closed" in str(err) or "unrecoverable" in str(err):
_LOGGER.warning(
"Alexa Media Player session is closed. Triggering Re-authentication flow."
)
# This tells Home Assistant to show a persistent notification requiring reauth
raise ConfigEntryAuthFailed(
"Amazon session closed. Please reconfigure your credentials."
) from err
raise
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
@@ -853,9 +857,25 @@ async def async_setup_entry(hass, config_entry):
_LOGGER.debug("[BOOT] login completed in %.2fs", time.monotonic() - _t)
_t = time.monotonic()
if await test_login_status(hass, config_entry, login):
# Registered only on success: HA processes async_on_unload
# callbacks on unload and on the ConfigEntryNotReady retry
# path, but not for a plain `False` return, so registering
# any earlier would leak a pair on every failed login check.
config_entry.async_on_unload(
hass.bus.async_listen("alexa_media_relogin_required", relogin)
)
config_entry.async_on_unload(
hass.bus.async_listen("alexa_media_relogin_success", login_success)
)
_LOGGER.debug("[BOOT] test_login_status in %.2fs", time.monotonic() - _t)
_t = time.monotonic()
await setup_alexa(hass, config_entry, login)
# Registered once the entry has loaded, so that Home Assistant
# removes it on unload. Any earlier and each setup retry, or failed
# login check, would leave another copy on the entry.
config_entry.async_on_unload(
config_entry.add_update_listener(update_listener)
)
_LOGGER.debug(
"[BOOT] setup_entry total: %.2fs", time.monotonic() - _boot_start
)
@@ -3128,8 +3148,6 @@ async def async_unload_entry(hass, entry) -> bool:
except Exception:
_LOGGER.error("Error unloading: %s", component)
await close_connections(hass, email)
for listener in hass.data[DATA_ALEXAMEDIA]["accounts"][email][DATA_LISTENER]:
listener()
hass.data[DATA_ALEXAMEDIA]["accounts"].pop(email)
# Clean up config flows in progress
flows_to_remove = []
+68 -12
View File
@@ -513,6 +513,8 @@ class AlexaMediaFlowHandler(config_entries.ConfigFlow):
)
else:
_LOGGER.debug("Using existing login")
if self.config.get("reauth") and self.config.get(CONF_EMAIL):
self.login.email = self.config[CONF_EMAIL]
if (
not self.config.get("reauth")
and user_input
@@ -623,6 +625,9 @@ class AlexaMediaFlowHandler(config_entries.ConfigFlow):
"""Handle reauth processing for the config flow."""
self._save_user_input_to_config(user_input)
self.config["reauth"] = True
self.reauth_entry = self.hass.config_entries.async_get_entry(
self.context["entry_id"]
)
reauth_schema = self._update_schema_defaults()
_LOGGER.debug(
"Creating reauth form with %s",
@@ -660,7 +665,16 @@ class AlexaMediaFlowHandler(config_entries.ConfigFlow):
email = login.email
_LOGGER.debug("Testing login status: %s", login.status)
if login.status and login.status.get("login_successful"):
existing_entry = await self.async_set_unique_id(f"{email} - {login.url}")
existing_entry = getattr(self, "reauth_entry", None)
_LOGGER.debug(
"Reauth existing entry: %s",
existing_entry.entry_id if existing_entry else None,
)
if existing_entry is None:
existing_entry = await self.async_set_unique_id(
f"{email} - {login.url}"
)
if self.config.get("reauth"):
self.config.pop("reauth")
if self.config.get(CONF_SECURITYCODE):
@@ -681,10 +695,61 @@ class AlexaMediaFlowHandler(config_entries.ConfigFlow):
)
self.hass.data[DATA_ALEXAMEDIA].setdefault("accounts", {})
self.hass.data[DATA_ALEXAMEDIA].setdefault("config_flows", {})
if existing_entry:
self.hass.config_entries.async_update_entry(
existing_entry, data=self.config
old_email = existing_entry.data.get(CONF_EMAIL)
accounts = self.hass.data[DATA_ALEXAMEDIA]["accounts"]
target_account = accounts.get(email)
if target_account is not None:
target_entry = target_account.get("config_entry")
if (
target_entry is None
or target_entry.entry_id != existing_entry.entry_id
):
_LOGGER.error(
"Cannot reauth %s: runtime account belongs to "
"another config entry",
hide_email(email),
)
return self.async_abort(reason="already_configured")
# Runtime accounts only exist for loaded entries, so also check
# every config entry (disabled or not yet loaded) for ownership.
new_unique_id = f"{login.email} - {login.url}"
for other_entry in self.hass.config_entries.async_entries(DOMAIN):
if other_entry.entry_id == existing_entry.entry_id:
continue
if other_entry.unique_id == new_unique_id:
_LOGGER.error(
"Cannot reauth %s: another config entry already "
"owns this account",
hide_email(email),
)
return self.async_abort(reason="already_configured")
if old_email and old_email != email and old_email in accounts:
_LOGGER.debug(
"Moving Alexa runtime account from %s to %s",
hide_email(old_email),
hide_email(email),
)
account_data = accounts.pop(old_email)
account_data["config_entry"] = existing_entry
account_data["login_obj"] = self.login
accounts[email] = account_data
else:
if email in accounts:
accounts[email]["login_obj"] = self.login
self.hass.config_entries.async_update_entry(
existing_entry,
data=self.config,
title=f"{login.email} - {login.url}",
unique_id=f"{login.email} - {login.url}",
)
_LOGGER.debug("Reauth successful for %s", hide_email(email))
self.hass.bus.async_fire(
"alexa_media_relogin_success",
@@ -696,15 +761,6 @@ class AlexaMediaFlowHandler(config_entries.ConfigFlow):
self.hass,
notification_id,
)
if not self.hass.data[DATA_ALEXAMEDIA]["accounts"].get(
self.config[CONF_EMAIL]
):
self.hass.data[DATA_ALEXAMEDIA]["accounts"][
self.config[CONF_EMAIL]
] = {}
self.hass.data[DATA_ALEXAMEDIA]["accounts"][self.config[CONF_EMAIL]][
"login_obj"
] = self.login
self.hass.data[DATA_ALEXAMEDIA]["config_flows"][
f"{email} - {login.url}"
] = None
+2 -7
View File
@@ -8,11 +8,6 @@
"iot_class": "cloud_polling",
"issue_tracker": "https://github.com/alandtse/alexa_media_player/issues",
"loggers": ["alexapy", "authcaptureproxy"],
"requirements": [
"alexapy==1.30.1",
"packaging>=20.3",
"wrapt>=1.14.0",
"dictor>=0.1.12,<0.2"
],
"version": "5.16.1"
"requirements": ["alexapy==1.31.0", "wrapt>=1.14.0", "dictor>=0.1.12,<0.2"],
"version": "5.16.2"
}
+3 -2
View File
@@ -211,8 +211,9 @@ class AlexaNotificationService(BaseNotificationService):
try:
targets = json.loads(targets)
except json.JSONDecodeError:
_LOGGER.error("Target must be a valid json")
return
targets = [targets]
if targets is None:
targets = []
processed_targets = []
for target in targets:
_LOGGER.debug("Processing: %s", target)
+1
View File
@@ -215,6 +215,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
hass, entry, implementation
),
log_traces=current_options.get(CONF_LOG_TRACES, DEFAULT_LOG_TRACES),
state_yaml=hass.config.path("hilo_state.yaml"),
)
except (TimeoutError, client_exceptions.ClientConnectorError):
+2 -2
View File
@@ -11,6 +11,6 @@
"documentation": "https://github.com/dvd-dev/hilo",
"iot_class": "cloud_push",
"issue_tracker": "https://github.com/dvd-dev/hilo/issues",
"requirements": ["python-hilo==2026.9.1"],
"version": "2026.10.1"
"requirements": ["python-hilo==2026.10.1"],
"version": "2026.10.2"
}
+3 -1
View File
@@ -739,7 +739,9 @@ class HiloRewardSensor(HiloEntity, RestoreEntity, SensorEntity):
Platform.SENSOR,
)
LOG.debug("Setting up RewardSensor entity: %s", self._attr_name)
self._history_state_yaml: str = "hilo_eventhistory_state.yaml"
self._history_state_yaml: str = hilo._hass.config.path(
"hilo_eventhistory_state.yaml"
)
self.scan_interval = timedelta(seconds=REWARD_SCAN_INTERVAL)
self._state = 0
self._history = []
+20 -20
View File
@@ -4,7 +4,7 @@
"state": "ON",
"led_brightness": 100,
"countdown_to_turn_off": 0,
"voltage": 120.5,
"voltage": 120.3,
"countdown_to_turn_on": 0,
"ac_frequency": 60,
"power_factor": 0.11,
@@ -27,9 +27,9 @@
"led_brightness": 100,
"countdown_to_turn_off": 0,
"countdown_to_turn_on": 0,
"power": 2.1,
"current": 0.1,
"energy": 55.26,
"power": 1.9,
"current": 0.09,
"energy": 55.27,
"power_factor": 0.16,
"update": {
"state": "idle",
@@ -47,8 +47,8 @@
"countdown_to_turn_off": 0,
"voltage": 119.9,
"countdown_to_turn_on": 0,
"energy": 107.28,
"power_factor": 0.2,
"energy": 107.3,
"power_factor": 0.3,
"ac_frequency": 60,
"update": {
"state": "idle",
@@ -59,7 +59,7 @@
},
"linkquality": 145,
"power": 0.2,
"current": 0.01,
"current": 0.79,
"power_on_behavior": "on"
},
"0xb40e060fffe031e3": {
@@ -74,13 +74,13 @@
"led_brightness": 100,
"countdown_to_turn_off": 0,
"countdown_to_turn_on": 0,
"voltage": 118.6,
"voltage": 119.1,
"state": "ON",
"ac_frequency": 60,
"energy": 218.15,
"power": 102.1,
"current": 0.94,
"power_factor": 0.95,
"energy": 218.19,
"power": 100,
"current": 0.89,
"power_factor": 0.94,
"update": {
"state": "idle",
"installed_version": 268513381,
@@ -96,12 +96,12 @@
"countdown_to_turn_off": 0,
"countdown_to_turn_on": 0,
"voltage": 120.5,
"energy": 102.57,
"energy": 102.58,
"state": "ON",
"power": 14,
"current": 0.21,
"power": 14.2,
"current": 0.2,
"ac_frequency": 60,
"power_factor": 0.6,
"power_factor": 0.53,
"update": {
"state": "idle",
"installed_version": 268513381,
@@ -114,7 +114,7 @@
},
"0xffffb40e060895b3": {
"state": "ON",
"voltage": 120.3,
"voltage": 120.2,
"ac_frequency": 60,
"energy": 15.03,
"current": 0.01,
@@ -136,14 +136,14 @@
"0xffffb40e0608864e": {
"led_brightness": 100,
"countdown_to_turn_off": 0,
"voltage": 120.6,
"voltage": 121,
"energy": 36.91,
"countdown_to_turn_on": 0,
"state": "ON",
"current": 0.02,
"ac_frequency": 60,
"power": 0.4,
"power_factor": 0.18,
"power": 0.3,
"power_factor": 0.19,
"update": {
"state": "idle",
"installed_version": 268513381,