2021-05-11 17:36:40 +00:00
|
|
|
"""Helper methods for common tasks."""
|
|
|
|
from __future__ import annotations
|
|
|
|
|
2022-01-04 17:37:46 +00:00
|
|
|
from collections.abc import Callable
|
2021-05-11 17:36:40 +00:00
|
|
|
import logging
|
2022-01-04 17:37:46 +00:00
|
|
|
from typing import TYPE_CHECKING, TypeVar
|
2021-05-11 17:36:40 +00:00
|
|
|
|
2022-02-08 18:17:05 +00:00
|
|
|
from soco import SoCo
|
2021-07-22 22:40:30 +00:00
|
|
|
from soco.exceptions import SoCoException, SoCoUPnPException
|
2022-01-04 17:37:46 +00:00
|
|
|
from typing_extensions import Concatenate, ParamSpec
|
2021-05-11 17:36:40 +00:00
|
|
|
|
2021-11-22 00:48:57 +00:00
|
|
|
from homeassistant.helpers.dispatcher import dispatcher_send
|
|
|
|
|
|
|
|
from .const import SONOS_SPEAKER_ACTIVITY
|
2022-02-08 18:17:05 +00:00
|
|
|
from .exception import SonosUpdateError
|
2021-11-22 00:48:57 +00:00
|
|
|
|
|
|
|
if TYPE_CHECKING:
|
|
|
|
from .entity import SonosEntity
|
2022-02-08 18:17:05 +00:00
|
|
|
from .household_coordinator import SonosHouseholdCoordinator
|
2021-11-22 00:48:57 +00:00
|
|
|
from .speaker import SonosSpeaker
|
2021-06-17 09:09:57 +00:00
|
|
|
|
2021-07-12 16:24:12 +00:00
|
|
|
UID_PREFIX = "RINCON_"
|
|
|
|
UID_POSTFIX = "01400"
|
|
|
|
|
2021-05-11 17:36:40 +00:00
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
2022-02-08 18:17:05 +00:00
|
|
|
_T = TypeVar("_T", "SonosSpeaker", "SonosEntity", "SonosHouseholdCoordinator")
|
2022-01-04 17:37:46 +00:00
|
|
|
_R = TypeVar("_R")
|
|
|
|
_P = ParamSpec("_P")
|
|
|
|
|
2021-05-11 17:36:40 +00:00
|
|
|
|
2021-11-22 00:48:57 +00:00
|
|
|
def soco_error(
|
2022-02-08 18:17:05 +00:00
|
|
|
errorcodes: list[str] | None = None,
|
2022-01-04 17:37:46 +00:00
|
|
|
) -> Callable[ # type: ignore[misc]
|
|
|
|
[Callable[Concatenate[_T, _P], _R]], Callable[Concatenate[_T, _P], _R | None]
|
|
|
|
]:
|
2021-06-17 09:09:57 +00:00
|
|
|
"""Filter out specified UPnP errors and raise exceptions for service calls."""
|
2021-05-11 17:36:40 +00:00
|
|
|
|
2022-01-04 17:37:46 +00:00
|
|
|
def decorator(
|
|
|
|
funct: Callable[Concatenate[_T, _P], _R] # type: ignore[misc]
|
|
|
|
) -> Callable[Concatenate[_T, _P], _R | None]: # type: ignore[misc]
|
2021-05-11 17:36:40 +00:00
|
|
|
"""Decorate functions."""
|
|
|
|
|
2022-01-04 17:37:46 +00:00
|
|
|
def wrapper(self: _T, *args: _P.args, **kwargs: _P.kwargs) -> _R | None:
|
2021-05-11 17:36:40 +00:00
|
|
|
"""Wrap for all soco UPnP exception."""
|
2022-02-08 18:17:05 +00:00
|
|
|
args_soco = next((arg for arg in args if isinstance(arg, SoCo)), None)
|
2021-05-11 17:36:40 +00:00
|
|
|
try:
|
2021-11-22 00:48:57 +00:00
|
|
|
result = funct(self, *args, **kwargs)
|
2021-06-17 09:09:57 +00:00
|
|
|
except (OSError, SoCoException, SoCoUPnPException) as err:
|
|
|
|
error_code = getattr(err, "error_code", None)
|
2021-12-06 23:21:28 +00:00
|
|
|
function = funct.__qualname__
|
2021-06-17 09:09:57 +00:00
|
|
|
if errorcodes and error_code in errorcodes:
|
|
|
|
_LOGGER.debug(
|
|
|
|
"Error code %s ignored in call to %s", error_code, function
|
|
|
|
)
|
2021-11-22 00:48:57 +00:00
|
|
|
return None
|
|
|
|
|
2022-02-08 18:17:05 +00:00
|
|
|
# In order of preference:
|
|
|
|
# * SonosSpeaker instance
|
|
|
|
# * SoCo instance passed as an arg
|
|
|
|
# * SoCo instance (as self)
|
|
|
|
speaker_or_soco = getattr(self, "speaker", args_soco or self)
|
|
|
|
zone_name = speaker_or_soco.zone_name
|
2021-11-22 00:48:57 +00:00
|
|
|
# Prefer the entity_id if available, zone name as a fallback
|
|
|
|
# Needed as SonosSpeaker instances are not entities
|
|
|
|
target = getattr(self, "entity_id", zone_name)
|
|
|
|
message = f"Error calling {function} on {target}: {err}"
|
2022-02-08 18:17:05 +00:00
|
|
|
raise SonosUpdateError(message) from err
|
2021-11-22 00:48:57 +00:00
|
|
|
|
2022-02-08 18:17:05 +00:00
|
|
|
dispatch_soco = args_soco or self.soco
|
2021-11-22 00:48:57 +00:00
|
|
|
dispatcher_send(
|
2021-12-06 23:21:28 +00:00
|
|
|
self.hass,
|
2022-02-08 18:17:05 +00:00
|
|
|
f"{SONOS_SPEAKER_ACTIVITY}-{dispatch_soco.uid}",
|
2021-12-06 23:21:28 +00:00
|
|
|
funct.__qualname__,
|
2021-11-22 00:48:57 +00:00
|
|
|
)
|
|
|
|
return result
|
2021-05-11 17:36:40 +00:00
|
|
|
|
2022-01-04 17:37:46 +00:00
|
|
|
return wrapper
|
2021-05-11 17:36:40 +00:00
|
|
|
|
|
|
|
return decorator
|
2021-07-12 16:24:12 +00:00
|
|
|
|
|
|
|
|
|
|
|
def hostname_to_uid(hostname: str) -> str:
|
|
|
|
"""Convert a Sonos hostname to a uid."""
|
2021-11-11 06:31:08 +00:00
|
|
|
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.")
|
2021-07-12 16:24:12 +00:00
|
|
|
return f"{UID_PREFIX}{baseuid}{UID_POSTFIX}"
|