Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

revert: revert changes in v4.13.3 #2587

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 29 additions & 75 deletions custom_components/alexa_media/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -371,11 +371,6 @@ async def setup_alexa(hass, config_entry, login_obj: AlexaLogin):
# pylint: disable=too-many-statements,too-many-locals
"""Set up a alexa api based on host parameter."""

# Initialize throttling state and lock
last_dnd_update_times: dict[str, datetime] = {}
pending_dnd_updates: dict[str, bool] = {}
dnd_update_lock = asyncio.Lock()

async def async_update_data() -> Optional[AlexaEntityData]:
# noqa pylint: disable=too-many-branches
"""Fetch data from API endpoint.
Expand Down Expand Up @@ -649,17 +644,31 @@ async def async_update_data() -> Optional[AlexaEntityData]:
cleaned_config = config.copy()
cleaned_config.pop(CONF_PASSWORD, None)
# CONF_PASSWORD contains sensitive info which is no longer needed
# Load multiple platforms in parallel using async_forward_entry_setups
_LOGGER.debug("Loading platforms: %s", ", ".join(ALEXA_COMPONENTS))
try:
await hass.config_entries.async_forward_entry_setups(
config_entry, ALEXA_COMPONENTS
for component in ALEXA_COMPONENTS:
entry_setup = len(
hass.data[DATA_ALEXAMEDIA]["accounts"][email]["entities"][component]
)
except (asyncio.TimeoutError, TimeoutException) as ex:
_LOGGER.error(f"Error while loading platforms: {ex}")
raise ConfigEntryNotReady(
f"Timeout while loading platforms: {ex}"
) from ex
if not entry_setup:
_LOGGER.debug("Loading config entry for %s", component)
try:
await hass.config_entries.async_forward_entry_setups(
config_entry, [component]
)
except (asyncio.TimeoutError, TimeoutException) as ex:
raise ConfigEntryNotReady(
f"Timeout while loading config entry for {component}"
) from ex
else:
_LOGGER.debug("Loading %s", component)
hass.async_create_task(
async_load_platform(
hass,
component,
DOMAIN,
{CONF_NAME: DOMAIN, "config": cleaned_config},
cleaned_config,
)
)

hass.data[DATA_ALEXAMEDIA]["accounts"][email]["new_devices"] = False
# prune stale devices
Expand Down Expand Up @@ -834,76 +843,21 @@ async def update_bluetooth_state(login_obj, device_serial):
)
return None

async def schedule_update_dnd_state(email: str):
"""Schedule an update_dnd_state call after MIN_TIME_BETWEEN_FORCED_SCANS."""
await asyncio.sleep(MIN_TIME_BETWEEN_FORCED_SCANS)
async with dnd_update_lock:
if pending_dnd_updates.get(email, False):
pending_dnd_updates[email] = False
_LOGGER.debug(
"Executing scheduled forced DND update for %s", hide_email(email)
)
# Assume login_obj can be retrieved or passed appropriately
login_obj = hass.data[DATA_ALEXAMEDIA]["accounts"][email]["login_obj"]
await update_dnd_state(login_obj)

@util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS)
@_catch_login_errors
async def update_dnd_state(login_obj) -> None:
"""Update the DND state on websocket DND combo event."""
email = login_obj.email
now = datetime.utcnow()
"""Update the dnd state on ws dnd combo event."""
dnd = await AlexaAPI.get_dnd_state(login_obj)

async with dnd_update_lock:
last_run = last_dnd_update_times.get(email)
cooldown = timedelta(seconds=MIN_TIME_BETWEEN_SCANS)

if last_run and (now - last_run) < cooldown:
# If within cooldown, mark a pending update if not already marked
if not pending_dnd_updates.get(email, False):
pending_dnd_updates[email] = True
_LOGGER.debug(
"Throttling active for %s, scheduling a forced DND update.",
hide_email(email),
)
asyncio.create_task(schedule_update_dnd_state(email))
else:
_LOGGER.debug(
"Throttling active for %s, forced DND update already scheduled.",
hide_email(email),
)
return

# Update the last run time
last_dnd_update_times[email] = now

_LOGGER.debug("Updating DND state for %s", hide_email(email))

try:
# Fetch the DND state using the Alexa API
dnd = await AlexaAPI.get_dnd_state(login_obj)
except asyncio.TimeoutError:
_LOGGER.error(
"Timeout occurred while fetching DND state for %s", hide_email(email)
)
return
except Exception as e:
_LOGGER.error(
"Unexpected error while fetching DND state for %s: %s",
hide_email(email),
e,
)
return

# Check if DND data is valid and dispatch an update event
if dnd is not None and "doNotDisturbDeviceStatusList" in dnd:
async_dispatcher_send(
hass,
f"{DOMAIN}_{hide_email(email)}"[0:32],
{"dnd_update": dnd["doNotDisturbDeviceStatusList"]},
)
return
else:
_LOGGER.debug("%s: get_dnd_state failed: dnd:%s", hide_email(email), dnd)
_LOGGER.debug("%s: get_dnd_state failed: dnd:%s", hide_email(email), dnd)
return

async def http2_connect() -> HTTP2EchoClient:
"""Open HTTP2 Push connection.
Expand Down
2 changes: 1 addition & 1 deletion custom_components/alexa_media/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
PERCENTAGE,
)

__version__ = "4.13.3"
__version__ = "4.13.4"
PROJECT_URL = "https://github.com/alandtse/alexa_media_player/"
ISSUE_URL = f"{PROJECT_URL}issues"
NOTIFY_URL = f"{PROJECT_URL}wiki/Configuration%3A-Notification-Component#use-the-notifyalexa_media-service"
Expand Down
45 changes: 19 additions & 26 deletions custom_components/alexa_media/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,37 +225,30 @@ def report_relogin_required(hass, login, email) -> bool:


def _existing_serials(hass, login_obj) -> list:
"""Retrieve existing serial numbers for a given login object."""
email: str = login_obj.email
if (
DATA_ALEXAMEDIA in hass.data
and "accounts" in hass.data[DATA_ALEXAMEDIA]
and email in hass.data[DATA_ALEXAMEDIA]["accounts"]
):
existing_serials = list(
existing_serials = (
list(
hass.data[DATA_ALEXAMEDIA]["accounts"][email]["entities"][
"media_player"
].keys()
)
device_data = (
hass.data[DATA_ALEXAMEDIA]["accounts"][email]
.get("devices", {})
.get("media_player", {})
)
for serial in existing_serials:
device = device_data.get(serial, {})
if "appDeviceList" in device and device["appDeviceList"]:
apps = [
x["serialNumber"]
for x in device["appDeviceList"]
if "serialNumber" in x
]
existing_serials.extend(apps)
else:
_LOGGER.warning(
"No accounts data found for %s. Skipping serials retrieval.", email
)
existing_serials = []
if "entities" in (hass.data[DATA_ALEXAMEDIA]["accounts"][email])
else []
)
for serial in existing_serials:
device = hass.data[DATA_ALEXAMEDIA]["accounts"][email]["devices"][
"media_player"
][serial]
if "appDeviceList" in device and device["appDeviceList"]:
apps = list(
map(
lambda x: x["serialNumber"] if "serialNumber" in x else None,
device["appDeviceList"],
)
)
# _LOGGER.debug("Combining %s with %s",
# existing_serials, apps)
existing_serials = existing_serials + apps
return existing_serials


Expand Down
2 changes: 1 addition & 1 deletion custom_components/alexa_media/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@
"issue_tracker": "https://github.com/alandtse/alexa_media_player/issues",
"loggers": ["alexapy", "authcaptureproxy"],
"requirements": ["alexapy==1.29.2", "packaging>=20.3", "wrapt>=1.14.0"],
"version": "4.13.3"
"version": "4.13.4"
}
59 changes: 22 additions & 37 deletions custom_components/alexa_media/media_player.py
Original file line number Diff line number Diff line change
Expand Up @@ -776,12 +776,8 @@ async def async_select_source(self, source):
else:
await self.alexa_api.set_bluetooth(devices["address"])
self._source = source
# Safely access 'http2' setting
if not (
self.hass.data.get(DATA_ALEXAMEDIA, {})
.get("accounts", {})
.get(self._login.email, {})
.get("http2")
self.hass.data[DATA_ALEXAMEDIA]["accounts"][self._login.email]["http2"]
):
await self.async_update()

Expand Down Expand Up @@ -926,47 +922,35 @@ async def async_update(self):
except AttributeError:
pass
email = self._login.email

# Check if DATA_ALEXAMEDIA and 'accounts' exist
accounts_data = self.hass.data.get(DATA_ALEXAMEDIA, {}).get("accounts", {})
if (
self.entity_id is None # Device has not initialized yet
or email not in accounts_data
or email not in self.hass.data[DATA_ALEXAMEDIA]["accounts"]
or self._login.session.closed
):
self._assumed_state = True
self.available = False
return

# Safely access the device
device = accounts_data[email]["devices"]["media_player"].get(
self.device_serial_number
)
if not device:
_LOGGER.warning(
"Device serial number %s not found for account %s. Skipping update.",
self.device_serial_number,
hide_email(email),
)
self.available = False
return

# Safely access websocket_commands
device = self.hass.data[DATA_ALEXAMEDIA]["accounts"][email]["devices"][
"media_player"
][self.device_serial_number]
seen_commands = (
accounts_data[email]["websocket_commands"].keys()
if "websocket_commands" in accounts_data[email]
self.hass.data[DATA_ALEXAMEDIA]["accounts"][email][
"websocket_commands"
].keys()
if "websocket_commands"
in (self.hass.data[DATA_ALEXAMEDIA]["accounts"][email])
else None
)

await self.refresh(device, no_throttle=True)

# Safely access 'http2' setting
push_enabled = accounts_data[email].get("http2")

await self.refresh( # pylint: disable=unexpected-keyword-arg
device, no_throttle=True
)
push_enabled = (
self.hass.data[DATA_ALEXAMEDIA]["accounts"].get(email, {}).get("http2")
)
if (
self.state in [MediaPlayerState.PLAYING]
and
# Only enable polling if websocket not connected
# only enable polling if websocket not connected
(
not push_enabled
or not seen_commands
Expand All @@ -986,7 +970,7 @@ async def async_update(self):
):
_LOGGER.debug(
"%s: %s playing; scheduling update in %s seconds",
hide_email(email),
hide_email(self._login.email),
self.name,
PLAY_SCAN_INTERVAL,
)
Expand All @@ -999,8 +983,9 @@ async def async_update(self):
self._should_poll = False
if not push_enabled:
_LOGGER.debug(
"%s: Disabling polling and scheduling last update in 300 seconds for %s",
hide_email(email),
"%s: Disabling polling and scheduling last update in"
" 300 seconds for %s",
hide_email(self._login.email),
self.name,
)
async_call_later(
Expand All @@ -1011,7 +996,7 @@ async def async_update(self):
else:
_LOGGER.debug(
"%s: Disabling polling for %s",
hide_email(email),
hide_email(self._login.email),
self.name,
)
self._last_update = util.utcnow()
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "alexa_media_player"
version = "4.13.3"
version = "4.13.4"
description = "This is a custom component to allow control of Amazon Alexa devices in [Homeassistant](https://home-assistant.io) using the unofficial Alexa API."
authors = [
"Keaton Taylor <keatonstaylor@gmail.com>",
Expand Down
Loading