1 """Helper methods for common tasks."""
3 from __future__
import annotations
5 from collections.abc
import Callable
7 from typing
import TYPE_CHECKING, Any, Concatenate, overload
9 from requests.exceptions
import Timeout
11 from soco.exceptions
import SoCoException, SoCoUPnPException
15 from .const
import SONOS_SPEAKER_ACTIVITY
16 from .exception
import SonosUpdateError
19 from .entity
import SonosEntity
20 from .household_coordinator
import SonosHouseholdCoordinator
21 from .media
import SonosMedia
22 from .speaker
import SonosSpeaker
24 UID_PREFIX =
"RINCON_"
27 _LOGGER = logging.getLogger(__name__)
29 type _SonosEntitiesType = (
30 SonosSpeaker | SonosMedia | SonosEntity | SonosHouseholdCoordinator
32 type _FuncType[_T, **_P, _R] = Callable[Concatenate[_T, _P], _R]
33 type _ReturnFuncType[_T, **_P, _R] = Callable[Concatenate[_T, _P], _R |
None]
37 def soco_error[_T: _SonosEntitiesType, **_P, _R](
38 errorcodes:
None = ...,
39 ) -> Callable[[_FuncType[_T, _P, _R]], _FuncType[_T, _P, _R]]: ...
43 def soco_error[_T: _SonosEntitiesType, **_P, _R](
44 errorcodes: list[str],
45 ) -> Callable[[_FuncType[_T, _P, _R]], _ReturnFuncType[_T, _P, _R]]: ...
48 def soco_error[_T: _SonosEntitiesType, **_P, _R](
49 errorcodes: list[str] |
None =
None,
50 ) -> Callable[[_FuncType[_T, _P, _R]], _ReturnFuncType[_T, _P, _R]]:
51 """Filter out specified UPnP errors and raise exceptions for service calls."""
53 def decorator(funct: _FuncType[_T, _P, _R]) -> _ReturnFuncType[_T, _P, _R]:
54 """Decorate functions."""
56 def wrapper(self: _T, *args: _P.args, **kwargs: _P.kwargs) -> _R |
None:
57 """Wrap for all soco UPnP exception."""
58 args_soco = next((arg
for arg
in args
if isinstance(arg, SoCo)),
None)
60 result = funct(self, *args, **kwargs)
61 except (OSError, SoCoException, SoCoUPnPException, Timeout)
as err:
62 error_code = getattr(err,
"error_code",
None)
63 function = funct.__qualname__
64 if errorcodes
and error_code
in errorcodes:
66 "Error code %s ignored in call to %s", error_code, function
71 raise RuntimeError(
"Unexpected use of soco_error")
from err
73 message = f
"Error calling {function} on {target}: {err}"
76 dispatch_soco = args_soco
or self.soco
79 f
"{SONOS_SPEAKER_ACTIVITY}-{dispatch_soco.uid}",
90 """Extract the best available target identifier from the provided instance object."""
91 if entity_id := getattr(instance,
"entity_id",
None):
94 if zone_name := getattr(instance,
"zone_name",
None):
97 if speaker := getattr(instance,
"speaker",
None):
99 return speaker.zone_name
100 if soco := getattr(instance,
"soco", fallback_soco):
103 return soco._player_name
or soco.ip_address
108 """Convert a Sonos hostname to a uid."""
109 if hostname.startswith(
"Sonos-"):
110 baseuid = hostname.removeprefix(
"Sonos-").replace(
".local.",
"")
111 elif hostname.startswith(
"sonos"):
112 baseuid = hostname.removeprefix(
"sonos").replace(
".local.",
"")
114 raise ValueError(f
"{hostname} is not a sonos device.")
115 return f
"{UID_PREFIX}{baseuid}{UID_POSTFIX}"
119 """Ensure I/O attributes are cached and return visible zones."""
120 _ = soco.household_id
122 return soco.visible_zones
str hostname_to_uid(str hostname)
str|None _find_target_identifier(Any instance, SoCo|None fallback_soco)
set[SoCo] sync_get_visible_zones(SoCo soco)
None dispatcher_send(HomeAssistant hass, str signal, *Any args)