Skip to content
34 changes: 26 additions & 8 deletions homeassistant/components/anthemav/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""The Anthem A/V Receivers integration."""

import asyncio
import logging

import anthemav
Expand All @@ -20,7 +21,13 @@
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
from homeassistant.helpers.dispatcher import async_dispatcher_send

from .const import ANTHEMAV_UPDATE_SIGNAL, DEVICE_TIMEOUT_SECONDS, DOMAIN, MANUFACTURER
from .const import (
ANTHEMAV_UPDATE_SIGNAL,
CONNECT_TIMEOUT_SECONDS,
DEVICE_TIMEOUT_SECONDS,
DOMAIN,
MANUFACTURER,
)

type AnthemavConfigEntry = ConfigEntry[anthemav.Connection]

Expand All @@ -39,16 +46,27 @@ def async_anthemav_update_callback(message: str) -> None:
async_dispatcher_send(hass, f"{ANTHEMAV_UPDATE_SIGNAL}_{entry.entry_id}")

try:
avr = await anthemav.Connection.create(
host=entry.data[CONF_HOST],
port=entry.data[CONF_PORT],
update_callback=async_anthemav_update_callback,
)
# anthemav.Connection.create() retries the initial connection
# internally with its own backoff and only returns once it
# succeeds, so it will not raise OSError on its own when the
# receiver is unreachable. Bound the attempt so an unreachable
# receiver fails fast into the normal ConfigEntryNotReady retry
# path, rather than blocking setup (and, at startup, Home
# Assistant's bootstrap) for as long as the receiver stays off.
Comment thread
Copilot marked this conversation as resolved.
Outdated
async with asyncio.timeout(CONNECT_TIMEOUT_SECONDS):
avr = await anthemav.Connection.create(
host=entry.data[CONF_HOST],
port=entry.data[CONF_PORT],
update_callback=async_anthemav_update_callback,
)

# Wait for the zones to be initialised based on the model
await avr.protocol.wait_for_device_initialised(DEVICE_TIMEOUT_SECONDS)
except (OSError, DeviceError) as err:
raise ConfigEntryNotReady from err
except (OSError, DeviceError, TimeoutError) as err:
raise ConfigEntryNotReady(
f"Unable to connect to Anthem AVR at {entry.data[CONF_HOST]}:"
f"{entry.data[CONF_PORT]}"
) from err
Comment thread
Copilot marked this conversation as resolved.

entry.runtime_data = avr

Expand Down
8 changes: 8 additions & 0 deletions homeassistant/components/anthemav/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,11 @@
DOMAIN = "anthemav"
MANUFACTURER = "Anthem"
DEVICE_TIMEOUT_SECONDS = 4.0
# anthemav.Connection.create() retries its initial connection attempt
# internally (with exponential backoff) and only returns once it succeeds,
# so it does not fail on its own when the receiver is unreachable. Bound it
# here instead of relying on Home Assistant's global bootstrap timeout,
# which would otherwise let one unreachable-at-boot receiver block startup
# for minutes and can collaterally cancel other integrations still setting
# up alongside it.
Comment thread
Copilot marked this conversation as resolved.
Outdated
CONNECT_TIMEOUT_SECONDS = 10.0
29 changes: 29 additions & 0 deletions tests/components/anthemav/test_init.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Test the Anthem A/V Receivers config flow."""

import asyncio
from collections.abc import Callable
from unittest.mock import ANY, AsyncMock, patch

Expand Down Expand Up @@ -72,6 +73,34 @@ async def test_config_entry_not_ready_when_oserror(
assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY


async def test_config_entry_not_ready_when_connect_hangs(
hass: HomeAssistant, mock_config_entry: MockConfigEntry
) -> None:
"""Test setup fails fast (instead of hanging) when the AVR never connects.

anthemav.Connection.create() retries its initial connection internally
and only returns once it succeeds, so it never raises OSError on its
own when the receiver is unreachable — nothing bounds that wait except
our own timeout. Simulate that by having the mocked create() hang
indefinitely, and confirm setup still resolves to SETUP_RETRY rather
than blocking forever.
"""
Comment thread
Copilot marked this conversation as resolved.
Outdated
with (
patch(
"homeassistant.components.anthemav.CONNECT_TIMEOUT_SECONDS",
0.01,
),
patch(
"anthemav.Connection.create",
side_effect=lambda *args, **kwargs: asyncio.sleep(3600),
),
):
mock_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY


async def test_anthemav_dispatcher_signal(
hass: HomeAssistant,
mock_connection_create: AsyncMock,
Expand Down
Loading