"""Helper methods for common tasks.""" from __future__ import annotations import logging from typing import TYPE_CHECKING, Any, Callable, TypeVar, cast from soco.exceptions import SoCoException, SoCoUPnPException from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.dispatcher import dispatcher_send from .const import SONOS_SPEAKER_ACTIVITY from .exception import SpeakerUnavailable if TYPE_CHECKING: from .entity import SonosEntity from .speaker import SonosSpeaker UID_PREFIX = "RINCON_" UID_POSTFIX = "01400" WrapFuncType = TypeVar("WrapFuncType", bound=Callable[..., Any]) _LOGGER = logging.getLogger(__name__) def soco_error( errorcodes: list[str] | None = None, raise_on_err: bool = True ) -> Callable: """Filter out specified UPnP errors and raise exceptions for service calls.""" def decorator(funct: WrapFuncType) -> WrapFuncType: """Decorate functions.""" def wrapper(self: SonosSpeaker | SonosEntity, *args: Any, **kwargs: Any) -> Any: """Wrap for all soco UPnP exception.""" try: result = funct(self, *args, **kwargs) except SpeakerUnavailable: return None except (OSError, SoCoException, SoCoUPnPException) as err: error_code = getattr(err, "error_code", None) function = funct.__qualname__ if errorcodes and error_code in errorcodes: _LOGGER.debug( "Error code %s ignored in call to %s", error_code, function ) return None # Prefer the entity_id if available, zone name as a fallback # Needed as SonosSpeaker instances are not entities zone_name = getattr(self, "speaker", self).zone_name target = getattr(self, "entity_id", zone_name) message = f"Error calling {function} on {target}: {err}" if raise_on_err: raise HomeAssistantError(message) from err _LOGGER.warning(message) return None dispatcher_send( self.hass, f"{SONOS_SPEAKER_ACTIVITY}-{self.soco.uid}", funct.__qualname__, ) return result return cast(WrapFuncType, wrapper) return decorator def hostname_to_uid(hostname: str) -> str: """Convert a Sonos hostname to a uid.""" if hostname.startswith("Sonos-"): baseuid = hostname.split("-")[1].replace(".local.", "") elif hostname.startswith("sonos"): baseuid = hostname[5:].replace(".local.", "") else: raise ValueError(f"{hostname} is not a sonos device.") return f"{UID_PREFIX}{baseuid}{UID_POSTFIX}"