1 """Base class for common speaker tasks."""
3 from __future__
import annotations
6 from collections.abc
import Callable, Collection, Coroutine
9 from functools
import partial
12 from typing
import TYPE_CHECKING, Any, cast
14 import defusedxml.ElementTree
as ET
15 from soco.core
import SoCo
16 from soco.events_base
import Event
as SonosEvent, SubscriptionBase
17 from soco.exceptions
import SoCoException, SoCoUPnPException
18 from soco.plugins.plex
import PlexPlugin
19 from soco.plugins.sharelink
import ShareLinkPlugin
20 from soco.snapshot
import Snapshot
21 from sonos_websocket
import SonosWebsocket
30 async_dispatcher_connect,
31 async_dispatcher_send,
37 from .alarms
import SonosAlarms
40 BATTERY_SCAN_INTERVAL,
46 SONOS_CREATE_AUDIO_FORMAT_SENSOR,
49 SONOS_CREATE_MEDIA_PLAYER,
50 SONOS_CREATE_MIC_SENSOR,
51 SONOS_CREATE_SWITCHES,
54 SONOS_SPEAKER_ACTIVITY,
57 SONOS_STATE_TRANSITIONING,
62 from .exception
import S1BatteryMissing, SonosSubscriptionsFailed, SonosUpdateError
63 from .favorites
import SonosFavorites
64 from .helpers
import soco_error
65 from .media
import SonosMedia
66 from .statistics
import ActivityStatistics, EventStatistics
69 from .
import SonosData
72 RESUB_COOLDOWN_SECONDS = 10.0
75 "NOT_CHARGING":
False,
77 SUBSCRIPTION_SERVICES = {
85 SUPPORTED_VANISH_REASONS = (
"powered off",
"sleeping",
"switch to bluetooth",
"upgrade")
86 UNUSED_DEVICE_KEYS = [
"SPID",
"TargetRoomName"]
89 _LOGGER = logging.getLogger(__name__)
93 """Representation of a Sonos speaker."""
99 speaker_info: dict[str, Any],
100 zone_group_state_sub: SubscriptionBase |
None,
102 """Initialize a SonosSpeaker."""
104 self.data: SonosData = hass.data[DATA_SONOS]
106 self.
websocketwebsocket: SonosWebsocket |
None =
None
107 self.household_id: str = soco.household_id
114 self.hardware_version: str = speaker_info[
"hardware_version"]
115 self.software_version: str = speaker_info[
"software_version"]
116 self.mac_address: str = speaker_info[
"mac_address"]
117 self.model_name: str = speaker_info[
"model_name"]
118 self.model_number: str = speaker_info[
"model_number"]
119 self.uid: str = speaker_info[
"uid"]
120 self.version: str = speaker_info[
"display_version"]
121 self.zone_name: str = speaker_info[
"zone_name"]
124 self.subscriptions_failed: bool =
False
126 if zone_group_state_sub:
131 self._last_event_cache: dict[str, Any] = {}
137 self.
_poll_timer_poll_timer: Callable |
None =
None
140 self.dispatchers: list[Callable] = []
148 self.
volumevolume: int |
None =
None
149 self.
mutedmuted: bool |
None =
None
151 self.
balancebalance: tuple[int, int] |
None =
None
152 self.bass: int |
None =
None
153 self.treble: int |
None =
None
154 self.
loudnessloudness: bool |
None =
None
157 self.audio_delay: int |
None =
None
158 self.dialog_level: bool |
None =
None
159 self.night_mode: bool |
None =
None
160 self.sub_enabled: bool |
None =
None
161 self.sub_crossover: int |
None =
None
162 self.sub_gain: int |
None =
None
163 self.surround_enabled: bool |
None =
None
164 self.surround_mode: bool |
None =
None
165 self.surround_level: int |
None =
None
166 self.music_surround_level: int |
None =
None
169 self.buttons_enabled: bool |
None =
None
171 self.status_light: bool |
None =
None
174 self.
coordinatorcoordinator: SonosSpeaker |
None =
None
175 self.
sonos_groupsonos_group: list[SonosSpeaker] = [self]
179 self._group_members_missing: set[str] = set()
182 self, entry: ConfigEntry, has_battery: bool, dispatches: list[tuple[Any, ...]]
184 """Complete setup in async context."""
192 self.
socosoco.ip_address,
193 player_id=self.
socosoco.uid,
197 dispatch_pairs: tuple[tuple[str, Callable[..., Any]], ...] = (
200 (f
"{SONOS_REBOOTED}-{self.soco.uid}", self.
async_rebootedasync_rebooted),
201 (f
"{SONOS_SPEAKER_ACTIVITY}-{self.soco.uid}", self.
speaker_activityspeaker_activity),
202 (f
"{SONOS_VANISHED}-{self.soco.uid}", self.
async_vanishedasync_vanished),
205 for signal, target
in dispatch_pairs:
206 entry.async_on_unload(
214 for dispatch
in dispatches:
219 def setup(self, entry: ConfigEntry) ->
None:
220 """Run initial setup of the speaker."""
221 self.
mediamedia.play_mode = self.
socosoco.play_mode
225 self.
mediamedia.poll_media()
227 dispatches: list[tuple[Any, ...]] = [(SONOS_CREATE_LEVELS, self)]
229 if audio_format := self.
socosoco.soundbar_audio_input_format:
230 dispatches.append((SONOS_CREATE_AUDIO_FORMAT_SENSOR, self, audio_format))
235 except SonosUpdateError:
236 _LOGGER.debug(
"No battery available for %s", self.zone_name)
241 if (mic_enabled := self.
socosoco.mic_enabled)
is not None:
243 dispatches.append((SONOS_CREATE_MIC_SENSOR, self))
246 alarm.alarm_id
for alarm
in self.
alarmsalarms
if alarm.zone.uid == self.
socosoco.uid
248 dispatches.append((SONOS_CREATE_ALARM, self, new_alarms))
250 dispatches.append((SONOS_CREATE_SWITCHES, self))
251 dispatches.append((SONOS_CREATE_MEDIA_PLAYER, self))
252 dispatches.append((SONOS_SPEAKER_ADDED, self.
socosoco.uid))
254 self.
hasshass.create_task(self.
async_setupasync_setup(entry, has_battery, dispatches))
260 """Write states for associated SonosEntity instances."""
265 """Write states for associated SonosEntity instances."""
273 """Return the SonosAlarms instance for this household."""
274 return self.data.alarms[self.household_id]
278 """Return the SonosFavorites instance for this household."""
279 return self.data.favorites[self.household_id]
283 """Return true if player is a coordinator."""
288 """Cache the PlexPlugin instance for this speaker."""
295 """Cache the ShareLinkPlugin instance for this speaker."""
302 """Return the current subscription callback address."""
304 addr, port = self.
_subscriptions_subscriptions[0].event_listener.address
305 return ":".
join([addr,
str(port)])
309 """Return a list of missing service subscriptions."""
310 subscribed_services = {sub.service.service_type
for sub
in self.
_subscriptions_subscriptions}
311 return SUBSCRIPTION_SERVICES - subscribed_services
317 self, result: Any, event: str, level: int = logging.DEBUG
319 """Log a message if a subscription action (create/renew/stop) results in an exception."""
320 if not isinstance(result, Exception):
323 if isinstance(result, asyncio.exceptions.TimeoutError):
324 message =
"Request timed out"
327 message =
str(result)
328 exc_info = result
if not str(result)
else None
332 "%s failed for %s: %s",
340 """Initiate event subscriptions under an async lock."""
347 except SonosSubscriptionsFailed:
348 _LOGGER.warning(
"Creating subscriptions failed for %s", self.zone_name)
352 """Create event subscriptions."""
357 if not subscriptions:
360 _LOGGER.debug(
"Creating subscriptions for %s", self.zone_name)
361 results = await asyncio.gather(*subscriptions, return_exceptions=
True)
362 for result
in results:
364 result,
"Creating subscription", logging.WARNING
367 if any(isinstance(result, Exception)
for result
in results):
368 raise SonosSubscriptionsFailed
376 async_dispatcher_send,
378 f
"{SONOS_FALLBACK_POLL}-{self.soco.uid}",
384 self, target: SubscriptionBase, sub_callback: Callable
386 """Create a Sonos subscription."""
387 subscription = await target.subscribe(
388 auto_renew=
True, requested_timeout=SUBSCRIPTION_TIMEOUT
390 subscription.callback = sub_callback
395 """Cancel all subscriptions."""
398 _LOGGER.debug(
"Unsubscribing from events for %s", self.zone_name)
399 results = await asyncio.gather(
400 *(subscription.unsubscribe()
for subscription
in self.
_subscriptions_subscriptions),
401 return_exceptions=
True,
403 for result
in results:
409 """Handle a failed subscription renewal."""
410 self.
hasshass.async_create_background_task(
411 self.
_async_renew_failed_async_renew_failed(exception),
"sonos renew failed", eager_start=
True
415 """Mark the speaker as offline after a subscription renewal failure.
417 This is to reset the state to allow a future clean subscription attempt.
427 """Handle callback event and route as needed."""
430 "Received event, cancelling poll timer for %s", self.zone_name
435 self.
speaker_activityspeaker_activity(f
"{event.service.service_type} subscription")
436 self.event_stats.receive(event)
439 if last_event := self._last_event_cache.
get(event.service.service_type):
440 if event.variables.items() <= last_event.items():
441 self.event_stats.duplicate(event)
445 self._last_event_cache[event.service.service_type] = event.variables
447 dispatcher(self, event)
451 """Add the soco instance associated with the event to the callback."""
452 if "alarm_list_version" not in event.variables:
454 self.
hasshass.async_create_background_task(
455 self.
alarmsalarms.async_process_event(event, self),
456 "sonos process event",
462 """Update device properties from an event."""
463 self.event_stats.process(event)
464 self.
hasshass.async_create_background_task(
466 "sonos device properties",
471 """Update device properties from an event."""
472 if "mic_enabled" in event.variables:
473 mic_exists = self.
mic_enabledmic_enabled
is not None
474 self.
mic_enabledmic_enabled = bool(
int(event.variables[
"mic_enabled"]))
478 if more_info := event.variables.get(
"more_info"):
485 """Add the soco instance associated with the event to the callback."""
486 if "favorites_update_id" not in event.variables:
488 if "container_update_i_ds" not in event.variables:
490 self.
hasshass.async_create_background_task(
491 self.
favoritesfavorites.async_process_event(event, self),
492 "sonos dispatch favorites",
498 """Update information about currently playing media from an event."""
504 av_transport_uri = event.variables.get(
"av_transport_uri",
"")
505 current_track_uri = event.variables.get(
"current_track_uri",
"")
506 if av_transport_uri == current_track_uri
and av_transport_uri.startswith(
509 new_coordinator_uid = av_transport_uri.split(
":")[-1]
510 if new_coordinator_speaker := self.data.discovered.get(new_coordinator_uid):
512 "Media update coordinator (%s) received for %s",
513 new_coordinator_speaker.zone_name,
519 "Media update coordinator (%s) for %s not yet available",
525 if crossfade := event.variables.get(
"current_crossfade_mode"):
526 crossfade = bool(
int(crossfade))
532 if (new_status := event.variables.get(
"transport_state"))
is None:
536 if new_status == SONOS_STATE_TRANSITIONING:
539 self.event_stats.process(event)
540 self.
hasshass.async_add_executor_job(
541 self.
mediamedia.update_media_from_event, event.variables
546 """Update information about currently volume settings."""
547 self.event_stats.process(event)
548 variables = event.variables
550 if "volume" in variables:
551 volume = variables[
"volume"]
553 if "LF" in volume
and "RF" in volume:
556 if "mute" in variables:
557 self.
mutedmuted = variables[
"mute"][
"Master"] ==
"1"
559 if loudness := variables.get(
"loudness"):
569 if bool_var
in variables:
570 setattr(self, bool_var, variables[bool_var] ==
"1")
579 "music_surround_level",
581 if int_var
in variables:
582 setattr(self, int_var, variables[int_var])
591 """Test device availability. Failure will raise SonosUpdateError."""
592 self.
socosoco.renderingControl.GetVolume(
593 [(
"InstanceID", 0), (
"Channel",
"Master")], timeout=1
598 """Track the last activity on this speaker, set availability and resubscribe."""
602 "Activity on %s from %s while in cooldown, ignoring",
609 _LOGGER.debug(
"Activity on %s from %s", self.zone_name, source)
611 self.activity_stats.activity(source, self.
_last_activity_last_activity)
614 if not was_available:
620 """Validate availability of the speaker based on recent activity."""
623 if time.monotonic() - self.
_last_activity_last_activity < AVAILABILITY_TIMEOUT:
626 self.
hasshass.async_create_background_task(
628 f
"sonos {self.uid} {self.zone_name} ping",
633 """Validate availability of the speaker based on recent activity."""
635 await self.
hasshass.async_add_executor_job(self.
pingping)
636 except SonosUpdateError:
638 "No recent activity and cannot reach %s, marking unavailable",
644 """Handle removal of speaker when unavailable."""
650 """Handle removal of speaker when unavailable."""
656 _LOGGER.debug(
"Starting resubscription cooldown for %s", self.zone_name)
669 self.data.discovery_known.discard(self.
socosoco.uid)
672 """Handle removal of speaker when marked as vanished."""
676 "%s has vanished (%s), marking unavailable", self.zone_name, reason
681 """Handle a detected speaker reboot."""
682 _LOGGER.debug(
"%s rebooted, reconnecting", self.zone_name)
691 """Fetch battery_info for the speaker."""
692 battery_info = self.
socosoco.get_battery_info()
695 raise S1BatteryMissing
699 """Update battery info using a SonosEvent payload value."""
700 battery_dict =
dict(x.split(
":")
for x
in more_info.split(
","))
701 for unused
in UNUSED_DEVICE_KEYS:
702 battery_dict.pop(unused,
None)
705 if "BattChg" not in battery_dict:
708 "Unknown device properties update for %s (%s),"
709 " please report an issue: '%s'"
719 is_charging = EVENT_CHARGING[battery_dict[
"BattChg"]]
726 "Level":
int(battery_dict[
"BattPct"]),
727 "PowerSource":
"EXTERNAL" if is_charging
else "BATTERY",
732 "S1 firmware detected on %s, battery info may update infrequently",
738 if is_charging == self.
chargingcharging:
740 elif not is_charging:
742 self.
battery_infobattery_info[
"PowerSource"] =
"BATTERY"
749 except SonosUpdateError
as err:
750 _LOGGER.debug(
"Could not request current power source: %s", err)
754 """Return the name of the current power source.
756 Observed to be either BATTERY or SONOS_CHARGING_RING or USB_POWER.
758 May be an empty dict if used with an S1 Move.
764 """Return the charging status of the speaker."""
770 """Poll the device for the current battery state."""
784 except SonosUpdateError
as err:
785 _LOGGER.debug(
"Could not poll battery info: %s", err)
793 """Update group topology when polling."""
798 """Update group topology if uid is missing."""
799 if uid
not in self._group_members_missing:
801 missing_zone = self.data.discovered[uid].zone_name
803 "%s was missing, adding to %s group", missing_zone, self.zone_name
809 """Handle callback for topology change event."""
810 if xml := event.variables.get(
"zone_group_state"):
811 zgs = ET.fromstring(xml)
812 for vanished_device
in zgs.find(
"VanishedDevices")
or []:
814 reason := vanished_device.get(
"Reason")
815 )
not in SUPPORTED_VANISH_REASONS:
817 "Ignoring %s marked %s as vanished with reason: %s",
819 vanished_device.get(
"ZoneName"),
823 uid = vanished_device.get(
"UUID")
826 f
"{SONOS_VANISHED}-{uid}",
829 self.event_stats.process(event)
830 self.
hasshass.async_create_background_task(
832 name=f
"sonos group update {self.zone_name}",
837 """Handle callback for topology change event."""
839 def _get_soco_group() -> list[str]:
840 """Ask SoCo cache for existing topology."""
841 coordinator_uid = self.
socosoco.uid
844 with contextlib.suppress(OSError, SoCoException):
845 if self.
socosoco.group
and self.
socosoco.group.coordinator:
846 coordinator_uid = self.
socosoco.group.coordinator.uid
849 for p
in self.
socosoco.group.members
850 if p.uid != coordinator_uid
and p.is_visible
853 return [coordinator_uid, *joined_uids]
855 async
def _async_extract_group(event: SonosEvent |
None) -> list[str]:
856 """Extract group layout from a topology event."""
857 if group := (event
and getattr(event,
"zone_player_uui_ds_in_group",
None)):
858 assert isinstance(group, str)
859 return group.split(
",")
861 return await self.
hasshass.async_add_executor_job(_get_soco_group)
864 def _async_regroup(group: list[str]) ->
None:
865 """Rebuild internal group layout."""
866 _LOGGER.debug(
"async_regroup %s %s", self.zone_name, group)
868 group == [self.
socosoco.uid]
875 "Zone %s Cleared coordinator [%s]",
884 entity_registry = er.async_get(self.
hasshass)
886 sonos_group_entities = []
889 speaker = self.data.discovered.get(uid)
891 self._group_members_missing.discard(uid)
892 sonos_group.append(speaker)
894 str, entity_registry.async_get_entity_id(MP_DOMAIN, DOMAIN, uid)
896 sonos_group_entities.append(entity_id)
898 self._group_members_missing.
add(uid)
900 "%s group member unavailable (%s), will try again",
916 for joined_uid
in group[1:]:
917 if joined_speaker := self.data.discovered.get(joined_uid):
918 joined_speaker.coordinator = self
919 joined_speaker.sonos_group = sonos_group
920 joined_speaker.sonos_group_entities = sonos_group_entities
922 "Zone %s Set coordinator [%s]",
923 joined_speaker.zone_name,
926 joined_speaker.async_write_entity_states()
928 _LOGGER.debug(
"Regrouped %s: %s", self.zone_name, self.
sonos_group_entitiessonos_group_entities)
930 async
def _async_handle_group_event(event: SonosEvent |
None) ->
None:
931 """Get async lock and handle event."""
933 async
with self.data.topology_condition:
934 group = await _async_extract_group(event)
936 if self.
socosoco.uid == group[0]:
937 _async_regroup(group)
939 self.data.topology_condition.notify_all()
941 return _async_handle_group_event(event)
944 def join(self, speakers: list[SonosSpeaker]) -> list[SonosSpeaker]:
945 """Form a group with other players."""
952 for speaker
in speakers:
953 if speaker.soco.uid != self.
socosoco.uid:
954 if speaker
not in group:
955 speaker.soco.join(self.
socosoco)
956 speaker.coordinator = self
957 group.append(speaker)
964 master: SonosSpeaker,
965 speakers: list[SonosSpeaker],
967 """Form a group with other players."""
968 async
with hass.data[DATA_SONOS].topology_condition:
969 group: list[SonosSpeaker] = await hass.async_add_executor_job(
970 master.join, speakers
972 await SonosSpeaker.wait_for_groups(hass, [group])
976 """Unjoin the player from a group."""
983 async
def unjoin_multi(hass: HomeAssistant, speakers: list[SonosSpeaker]) ->
None:
984 """Unjoin several players from their group."""
986 def _unjoin_all(speakers: list[SonosSpeaker]) ->
None:
989 coordinators = [s
for s
in speakers
if s.is_coordinator]
990 joined_speakers = [s
for s
in speakers
if not s.is_coordinator]
992 for speaker
in joined_speakers + coordinators:
995 async
with hass.data[DATA_SONOS].topology_condition:
996 await hass.async_add_executor_job(_unjoin_all, speakers)
997 await SonosSpeaker.wait_for_groups(hass, [[s]
for s
in speakers])
1001 """Snapshot the state of a player."""
1011 hass: HomeAssistant, speakers: list[SonosSpeaker], with_group: bool
1013 """Snapshot all the speakers and optionally their groups."""
1015 def _snapshot_all(speakers: Collection[SonosSpeaker]) ->
None:
1017 for speaker
in speakers:
1018 speaker.snapshot(with_group)
1021 speakers_set = set(speakers)
1023 for speaker
in list(speakers_set):
1024 speakers_set.update(speaker.sonos_group)
1026 async
with hass.data[DATA_SONOS].topology_condition:
1027 await hass.async_add_executor_job(_snapshot_all, speakers_set)
1031 """Restore a snapshotted state to a player."""
1035 except (TypeError, AssertionError, AttributeError, SoCoException)
as ex:
1037 _LOGGER.warning(
"Error on restore %s: %s", self.zone_name, ex)
1044 hass: HomeAssistant, speakers: list[SonosSpeaker], with_group: bool
1046 """Restore snapshots for all the speakers."""
1048 def _restore_groups(
1049 speakers: set[SonosSpeaker], with_group: bool
1050 ) -> list[list[SonosSpeaker]]:
1051 """Pause all current coordinators and restore groups."""
1052 for speaker
in (s
for s
in speakers
if s.is_coordinator):
1054 speaker.media.playback_status == SONOS_STATE_PLAYING
1055 and "Pause" in speaker.soco.available_actions
1058 speaker.soco.pause()
1059 except SoCoUPnPException
as exc:
1061 "Pause failed during restore of %s: %s",
1063 speaker.soco.available_actions,
1067 groups: list[list[SonosSpeaker]] = []
1075 speakers_to_unjoin = set()
1076 for speaker
in speakers:
1077 if speaker.sonos_group == speaker.snapshot_group:
1080 speakers_to_unjoin.update(
1083 for s
in speaker.sonos_group[1:]
1084 if s
not in speaker.snapshot_group
1088 for speaker
in speakers_to_unjoin:
1092 for speaker
in (s
for s
in speakers
if s.snapshot_group):
1093 assert len(speaker.snapshot_group)
1094 if speaker.snapshot_group[0] == speaker:
1095 if speaker.snapshot_group
not in (speaker.sonos_group, [speaker]):
1096 speaker.join(speaker.snapshot_group)
1097 groups.append(speaker.snapshot_group.copy())
1101 def _restore_players(speakers: Collection[SonosSpeaker]) ->
None:
1102 """Restore state of all players."""
1103 for speaker
in (s
for s
in speakers
if not s.is_coordinator):
1106 for speaker
in (s
for s
in speakers
if s.is_coordinator):
1110 speakers_set = {s
for s
in speakers
if s.soco_snapshot}
1111 if missing_snapshots := set(speakers) - speakers_set:
1113 "Restore failed, speakers are missing snapshots:"
1114 f
" {[s.zone_name for s in missing_snapshots]}"
1118 for speaker
in [s
for s
in speakers_set
if s.snapshot_group]:
1119 assert len(speaker.snapshot_group)
1120 speakers_set.update(speaker.snapshot_group)
1122 async
with hass.data[DATA_SONOS].topology_condition:
1123 groups = await hass.async_add_executor_job(
1124 _restore_groups, speakers_set, with_group
1126 await SonosSpeaker.wait_for_groups(hass, groups)
1127 await hass.async_add_executor_job(_restore_players, speakers_set)
1131 hass: HomeAssistant, groups: list[list[SonosSpeaker]]
1133 """Wait until all groups are present, or timeout."""
1135 def _test_groups(groups: list[list[SonosSpeaker]]) -> bool:
1136 """Return whether all groups exist now."""
1137 for group
in groups:
1138 coordinator = group[0]
1141 current_group = coordinator.sonos_group
1142 if coordinator != current_group[0]:
1146 if set(group[1:]) != set(current_group[1:]):
1152 async
with asyncio.timeout(5):
1153 while not _test_groups(groups):
1154 await hass.data[DATA_SONOS].topology_condition.wait()
1155 except TimeoutError:
1156 _LOGGER.warning(
"Timeout waiting for target groups %s", groups)
1158 any_speaker = next(iter(hass.data[DATA_SONOS].discovered.values()))
1159 any_speaker.soco.zone_group_state.clear_cache()
1166 """Update information about current volume settings."""
1170 _event_dispatchers = {
1171 "AlarmClock": async_dispatch_alarms,
1172 "AVTransport": async_dispatch_media_update,
1173 "ContentDirectory": async_dispatch_favorites,
1174 "DeviceProperties": async_dispatch_device_properties,
1175 "RenderingControl": async_update_volume,
1176 "ZoneGroupTopology": async_update_groups,
None log_subscription_result(self, Any result, str event, int level=logging.DEBUG)
None async_renew_failed(self, Exception exception)
None async_vanished(self, str reason)
None async_poll_battery(self, datetime.datetime|None now=None)
_resub_cooldown_expires_at
None _async_subscribe(self)
None async_write_entity_states(self)
PlexPlugin plex_plugin(self)
list[SonosSpeaker] join(self, list[SonosSpeaker] speakers)
None async_dispatch_device_properties(self, SonosEvent event)
None _async_renew_failed(self, Exception exception)
None restore_multi(HomeAssistant hass, list[SonosSpeaker] speakers, bool with_group)
None _async_check_activity(self)
None __init__(self, HomeAssistant hass, SoCo soco, dict[str, Any] speaker_info, SubscriptionBase|None zone_group_state_sub)
None _async_offline(self)
None async_update_groups(self, SonosEvent event)
None async_rebooted(self)
None write_entity_states(self)
None async_check_activity(self, datetime.datetime now)
None speaker_activity(self, str source)
str|None power_source(self)
None async_dispatch_alarms(self, SonosEvent event)
None async_update_device_properties(self, SonosEvent event)
None snapshot(self, bool with_group)
None snapshot_multi(HomeAssistant hass, list[SonosSpeaker] speakers, bool with_group)
dictionary _event_dispatchers
None wait_for_groups(HomeAssistant hass, list[list[SonosSpeaker]] groups)
None unjoin_multi(HomeAssistant hass, list[SonosSpeaker] speakers)
SonosFavorites favorites(self)
None _subscribe(self, SubscriptionBase target, Callable sub_callback)
None async_update_group_for_uid(self, str uid)
None async_subscribe(self)
Coroutine create_update_groups_coro(self, SonosEvent|None event=None)
None async_dispatch_media_update(self, SonosEvent event)
None setup(self, ConfigEntry entry)
set[str] missing_subscriptions(self)
None async_dispatch_favorites(self, SonosEvent event)
None join_multi(HomeAssistant hass, SonosSpeaker master, list[SonosSpeaker] speakers)
ShareLinkPlugin share_link(self)
dict[str, Any] fetch_battery_info(self)
None async_update_battery_info(self, str more_info)
bool is_coordinator(self)
None async_setup(self, ConfigEntry entry, bool has_battery, list[tuple[Any,...]] dispatches)
None async_dispatch_event(self, SonosEvent event)
str subscription_address(self)
None async_update_volume(self, SonosEvent event)
None async_unsubscribe(self)
bool add(self, _T matcher)
web.Response get(self, web.Request request, str config_key)
IssData update(pyiss.ISS iss)
aiohttp.ClientSession async_get_clientsession(HomeAssistant hass, bool verify_ssl=True, socket.AddressFamily family=socket.AF_UNSPEC, ssl_util.SSLCipherList ssl_cipher=ssl_util.SSLCipherList.PYTHON_DEFAULT)
Callable[[], None] async_dispatcher_connect(HomeAssistant hass, str signal, Callable[..., Any] target)
None dispatcher_send(HomeAssistant hass, str signal, *Any args)
None async_dispatcher_send(HomeAssistant hass, str signal, *Any args)
CALLBACK_TYPE async_track_time_interval(HomeAssistant hass, Callable[[datetime], Coroutine[Any, Any, None]|None] action, timedelta interval, *str|None name=None, bool|None cancel_on_shutdown=None)