Fix status update loop in bluesound integration (#123790)

* Fix retry loop for status update

* Use 'available' instead of _is_online

* Fix tests
This commit is contained in:
Louis Christ 2024-08-13 12:55:01 +02:00 committed by GitHub
parent 193a7b7360
commit 30994710e6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 55 additions and 21 deletions

View File

@ -244,7 +244,6 @@ class BluesoundPlayer(MediaPlayerEntity):
self._status: Status | None = None self._status: Status | None = None
self._inputs: list[Input] = [] self._inputs: list[Input] = []
self._presets: list[Preset] = [] self._presets: list[Preset] = []
self._is_online = False
self._muted = False self._muted = False
self._master: BluesoundPlayer | None = None self._master: BluesoundPlayer | None = None
self._is_master = False self._is_master = False
@ -312,20 +311,24 @@ class BluesoundPlayer(MediaPlayerEntity):
async def _start_poll_command(self): async def _start_poll_command(self):
"""Loop which polls the status of the player.""" """Loop which polls the status of the player."""
try: while True:
while True: try:
await self.async_update_status() await self.async_update_status()
except (TimeoutError, ClientError):
except (TimeoutError, ClientError): _LOGGER.error(
_LOGGER.error("Node %s:%s is offline, retrying later", self.host, self.port) "Node %s:%s is offline, retrying later", self.host, self.port
await asyncio.sleep(NODE_OFFLINE_CHECK_TIMEOUT) )
self.start_polling() await asyncio.sleep(NODE_OFFLINE_CHECK_TIMEOUT)
except CancelledError:
except CancelledError: _LOGGER.debug(
_LOGGER.debug("Stopping the polling of node %s:%s", self.host, self.port) "Stopping the polling of node %s:%s", self.host, self.port
except Exception: )
_LOGGER.exception("Unexpected error in %s:%s", self.host, self.port) return
raise except Exception:
_LOGGER.exception(
"Unexpected error in %s:%s, retrying later", self.host, self.port
)
await asyncio.sleep(NODE_OFFLINE_CHECK_TIMEOUT)
async def async_added_to_hass(self) -> None: async def async_added_to_hass(self) -> None:
"""Start the polling task.""" """Start the polling task."""
@ -348,7 +351,7 @@ class BluesoundPlayer(MediaPlayerEntity):
async def async_update(self) -> None: async def async_update(self) -> None:
"""Update internal status of the entity.""" """Update internal status of the entity."""
if not self._is_online: if not self.available:
return return
with suppress(TimeoutError): with suppress(TimeoutError):
@ -365,7 +368,7 @@ class BluesoundPlayer(MediaPlayerEntity):
try: try:
status = await self._player.status(etag=etag, poll_timeout=120, timeout=125) status = await self._player.status(etag=etag, poll_timeout=120, timeout=125)
self._is_online = True self._attr_available = True
self._last_status_update = dt_util.utcnow() self._last_status_update = dt_util.utcnow()
self._status = status self._status = status
@ -394,7 +397,7 @@ class BluesoundPlayer(MediaPlayerEntity):
self.async_write_ha_state() self.async_write_ha_state()
except (TimeoutError, ClientError): except (TimeoutError, ClientError):
self._is_online = False self._attr_available = False
self._last_status_update = None self._last_status_update = None
self._status = None self._status = None
self.async_write_ha_state() self.async_write_ha_state()

View File

@ -3,7 +3,7 @@
from collections.abc import Generator from collections.abc import Generator
from unittest.mock import AsyncMock, patch from unittest.mock import AsyncMock, patch
from pyblu import SyncStatus from pyblu import Status, SyncStatus
import pytest import pytest
from homeassistant.components.bluesound.const import DOMAIN from homeassistant.components.bluesound.const import DOMAIN
@ -39,6 +39,35 @@ def sync_status() -> SyncStatus:
) )
@pytest.fixture
def status() -> Status:
"""Return a status object."""
return Status(
etag="etag",
input_id=None,
service=None,
state="playing",
shuffle=False,
album=None,
artist=None,
name=None,
image=None,
volume=10,
volume_db=22.3,
mute=False,
mute_volume=None,
mute_volume_db=None,
seconds=2,
total_seconds=123.1,
can_seek=False,
sleep=0,
group_name=None,
group_volume=None,
indexing=False,
stream_url=None,
)
@pytest.fixture @pytest.fixture
def mock_setup_entry() -> Generator[AsyncMock]: def mock_setup_entry() -> Generator[AsyncMock]:
"""Override async_setup_entry.""" """Override async_setup_entry."""
@ -65,7 +94,7 @@ def mock_config_entry(hass: HomeAssistant) -> MockConfigEntry:
@pytest.fixture @pytest.fixture
def mock_player() -> Generator[AsyncMock]: def mock_player(status: Status) -> Generator[AsyncMock]:
"""Mock the player.""" """Mock the player."""
with ( with (
patch( patch(
@ -78,7 +107,7 @@ def mock_player() -> Generator[AsyncMock]:
): ):
player = mock_player.return_value player = mock_player.return_value
player.__aenter__.return_value = player player.__aenter__.return_value = player
player.status.return_value = None player.status.return_value = status
player.sync_status.return_value = SyncStatus( player.sync_status.return_value = SyncStatus(
etag="etag", etag="etag",
id="1.1.1.1:11000", id="1.1.1.1:11000",

View File

@ -41,7 +41,7 @@ async def test_user_flow_success(
async def test_user_flow_cannot_connect( async def test_user_flow_cannot_connect(
hass: HomeAssistant, mock_player: AsyncMock hass: HomeAssistant, mock_player: AsyncMock, mock_setup_entry: AsyncMock
) -> None: ) -> None:
"""Test we handle cannot connect error.""" """Test we handle cannot connect error."""
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
@ -76,6 +76,8 @@ async def test_user_flow_cannot_connect(
CONF_PORT: 11000, CONF_PORT: 11000,
} }
mock_setup_entry.assert_called_once()
async def test_user_flow_aleady_configured( async def test_user_flow_aleady_configured(
hass: HomeAssistant, hass: HomeAssistant,