Add error handling for Russound RIO async calls (#123756)

Add better error handling to Russound RIO
pull/123819/head
Noah Husby 2024-08-13 10:23:13 -04:00 committed by GitHub
parent 679baddd3d
commit 995ed77849
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 34 additions and 4 deletions

View File

@ -8,7 +8,7 @@ from aiorussound import Russound
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST, CONF_PORT, Platform from homeassistant.const import CONF_HOST, CONF_PORT, Platform
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryError from homeassistant.exceptions import ConfigEntryNotReady
from .const import CONNECT_TIMEOUT, RUSSOUND_RIO_EXCEPTIONS from .const import CONNECT_TIMEOUT, RUSSOUND_RIO_EXCEPTIONS
@ -22,13 +22,15 @@ type RussoundConfigEntry = ConfigEntry[Russound]
async def async_setup_entry(hass: HomeAssistant, entry: RussoundConfigEntry) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: RussoundConfigEntry) -> bool:
"""Set up a config entry.""" """Set up a config entry."""
russ = Russound(hass.loop, entry.data[CONF_HOST], entry.data[CONF_PORT]) host = entry.data[CONF_HOST]
port = entry.data[CONF_PORT]
russ = Russound(hass.loop, host, port)
try: try:
async with asyncio.timeout(CONNECT_TIMEOUT): async with asyncio.timeout(CONNECT_TIMEOUT):
await russ.connect() await russ.connect()
except RUSSOUND_RIO_EXCEPTIONS as err: except RUSSOUND_RIO_EXCEPTIONS as err:
raise ConfigEntryError(err) from err raise ConfigEntryNotReady(f"Error while connecting to {host}:{port}") from err
entry.runtime_data = russ entry.runtime_data = russ

View File

@ -2,7 +2,10 @@
from __future__ import annotations from __future__ import annotations
from collections.abc import Awaitable, Callable, Coroutine
from functools import wraps
import logging import logging
from typing import Any, Concatenate
from aiorussound import Source, Zone from aiorussound import Source, Zone
@ -17,12 +20,13 @@ from homeassistant.config_entries import SOURCE_IMPORT
from homeassistant.const import EVENT_HOMEASSISTANT_STOP from homeassistant.const import EVENT_HOMEASSISTANT_STOP
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant, callback from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant, callback
from homeassistant.data_entry_flow import FlowResultType from homeassistant.data_entry_flow import FlowResultType
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, DeviceInfo from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from . import RussoundConfigEntry from . import RUSSOUND_RIO_EXCEPTIONS, RussoundConfigEntry
from .const import DOMAIN, MP_FEATURES_BY_FLAG from .const import DOMAIN, MP_FEATURES_BY_FLAG
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -107,6 +111,24 @@ async def async_setup_entry(
async_add_entities(entities) async_add_entities(entities)
def command[_T: RussoundZoneDevice, **_P](
func: Callable[Concatenate[_T, _P], Awaitable[None]],
) -> Callable[Concatenate[_T, _P], Coroutine[Any, Any, None]]:
"""Wrap async calls to raise on request error."""
@wraps(func)
async def decorator(self: _T, *args: _P.args, **kwargs: _P.kwargs) -> None:
"""Wrap all command methods."""
try:
await func(self, *args, **kwargs)
except RUSSOUND_RIO_EXCEPTIONS as exc:
raise HomeAssistantError(
f"Error executing {func.__name__} on entity {self.entity_id},"
) from exc
return decorator
class RussoundZoneDevice(MediaPlayerEntity): class RussoundZoneDevice(MediaPlayerEntity):
"""Representation of a Russound Zone.""" """Representation of a Russound Zone."""
@ -221,19 +243,23 @@ class RussoundZoneDevice(MediaPlayerEntity):
""" """
return float(self._zone.volume or "0") / 50.0 return float(self._zone.volume or "0") / 50.0
@command
async def async_turn_off(self) -> None: async def async_turn_off(self) -> None:
"""Turn off the zone.""" """Turn off the zone."""
await self._zone.zone_off() await self._zone.zone_off()
@command
async def async_turn_on(self) -> None: async def async_turn_on(self) -> None:
"""Turn on the zone.""" """Turn on the zone."""
await self._zone.zone_on() await self._zone.zone_on()
@command
async def async_set_volume_level(self, volume: float) -> None: async def async_set_volume_level(self, volume: float) -> None:
"""Set the volume level.""" """Set the volume level."""
rvol = int(volume * 50.0) rvol = int(volume * 50.0)
await self._zone.set_volume(rvol) await self._zone.set_volume(rvol)
@command
async def async_select_source(self, source: str) -> None: async def async_select_source(self, source: str) -> None:
"""Select the source input for this zone.""" """Select the source input for this zone."""
for source_id, src in self._sources.items(): for source_id, src in self._sources.items():
@ -242,10 +268,12 @@ class RussoundZoneDevice(MediaPlayerEntity):
await self._zone.select_source(source_id) await self._zone.select_source(source_id)
break break
@command
async def async_volume_up(self) -> None: async def async_volume_up(self) -> None:
"""Step the volume up.""" """Step the volume up."""
await self._zone.volume_up() await self._zone.volume_up()
@command
async def async_volume_down(self) -> None: async def async_volume_down(self) -> None:
"""Step the volume down.""" """Step the volume down."""
await self._zone.volume_down() await self._zone.volume_down()