Skip to content
Draft
28 changes: 20 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,21 @@ 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,
)
# See CONNECT_TIMEOUT_SECONDS for why this needs a timeout.
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

entry.runtime_data = avr

Expand Down
3 changes: 3 additions & 0 deletions homeassistant/components/anthemav/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@
DOMAIN = "anthemav"
MANUFACTURER = "Anthem"
DEVICE_TIMEOUT_SECONDS = 4.0
# anthemav.Connection.create() retries internally and only returns once
# connected, so it never fails on its own when the receiver is unreachable.
CONNECT_TIMEOUT_SECONDS = 10.0
25 changes: 25 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,30 @@ 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."""

async def _hang(*args, **kwargs) -> None:
await asyncio.sleep(3600)

with (
patch(
"homeassistant.components.anthemav.CONNECT_TIMEOUT_SECONDS",
0.01,
),
patch(
"anthemav.Connection.create",
side_effect=_hang,
),
):
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