Home Assistant Unofficial Reference 2024.12.1
media_player.py
Go to the documentation of this file.
1 """Support for Russound multizone controllers using RIO Protocol."""
2 
3 from __future__ import annotations
4 
5 import logging
6 
7 from aiorussound import Controller
8 from aiorussound.models import PlayStatus, Source
9 from aiorussound.rio import ZoneControlSurface
10 
12  MediaPlayerDeviceClass,
13  MediaPlayerEntity,
14  MediaPlayerEntityFeature,
15  MediaPlayerState,
16  MediaType,
17 )
18 from homeassistant.config_entries import SOURCE_IMPORT
19 from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant
20 from homeassistant.data_entry_flow import FlowResultType
21 from homeassistant.helpers.entity_platform import AddEntitiesCallback
22 from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
23 from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
24 
25 from . import RussoundConfigEntry
26 from .const import DOMAIN, MP_FEATURES_BY_FLAG
27 from .entity import RussoundBaseEntity, command
28 
29 _LOGGER = logging.getLogger(__name__)
30 
31 
33  hass: HomeAssistant,
34  config: ConfigType,
35  async_add_entities: AddEntitiesCallback,
36  discovery_info: DiscoveryInfoType | None = None,
37 ) -> None:
38  """Set up the Russound RIO platform."""
39 
40  result = await hass.config_entries.flow.async_init(
41  DOMAIN,
42  context={"source": SOURCE_IMPORT},
43  data=config,
44  )
45  if (
46  result["type"] is FlowResultType.CREATE_ENTRY
47  or result["reason"] == "single_instance_allowed"
48  ):
50  hass,
51  HOMEASSISTANT_DOMAIN,
52  f"deprecated_yaml_{DOMAIN}",
53  breaks_in_ha_version="2025.2.0",
54  is_fixable=False,
55  issue_domain=DOMAIN,
56  severity=IssueSeverity.WARNING,
57  translation_key="deprecated_yaml",
58  translation_placeholders={
59  "domain": DOMAIN,
60  "integration_title": "Russound RIO",
61  },
62  )
63  return
65  hass,
66  DOMAIN,
67  f"deprecated_yaml_import_issue_{result['reason']}",
68  breaks_in_ha_version="2025.2.0",
69  is_fixable=False,
70  issue_domain=DOMAIN,
71  severity=IssueSeverity.WARNING,
72  translation_key=f"deprecated_yaml_import_issue_{result['reason']}",
73  translation_placeholders={
74  "domain": DOMAIN,
75  "integration_title": "Russound RIO",
76  },
77  )
78 
79 
81  hass: HomeAssistant,
82  entry: RussoundConfigEntry,
83  async_add_entities: AddEntitiesCallback,
84 ) -> None:
85  """Set up the Russound RIO platform."""
86  client = entry.runtime_data
87  sources = client.sources
88 
90  RussoundZoneDevice(controller, zone_id, sources)
91  for controller in client.controllers.values()
92  for zone_id in controller.zones
93  )
94 
95 
97  """Representation of a Russound Zone."""
98 
99  _attr_device_class = MediaPlayerDeviceClass.SPEAKER
100  _attr_media_content_type = MediaType.MUSIC
101  _attr_supported_features = (
102  MediaPlayerEntityFeature.VOLUME_SET
103  | MediaPlayerEntityFeature.VOLUME_STEP
104  | MediaPlayerEntityFeature.TURN_ON
105  | MediaPlayerEntityFeature.TURN_OFF
106  | MediaPlayerEntityFeature.SELECT_SOURCE
107  )
108 
109  def __init__(
110  self, controller: Controller, zone_id: int, sources: dict[int, Source]
111  ) -> None:
112  """Initialize the zone device."""
113  super().__init__(controller)
114  self._zone_id_zone_id = zone_id
115  _zone = self._zone_zone
116  self._sources_sources = sources
117  self._attr_name_attr_name = _zone.name
118  self._attr_unique_id_attr_unique_id = f"{self._primary_mac_address}-{_zone.device_str}"
119  for flag, feature in MP_FEATURES_BY_FLAG.items():
120  if flag in self._client_client.supported_features:
121  self._attr_supported_features_attr_supported_features |= feature
122 
123  @property
124  def _zone(self) -> ZoneControlSurface:
125  return self._controller_controller.zones[self._zone_id_zone_id]
126 
127  @property
128  def _source(self) -> Source:
129  return self._zone_zone.fetch_current_source()
130 
131  @property
132  def state(self) -> MediaPlayerState | None:
133  """Return the state of the device."""
134  status = self._zone_zone.status
135  play_status = self._source_source.play_status
136  if not status:
137  return MediaPlayerState.OFF
138  if play_status == PlayStatus.PLAYING:
139  return MediaPlayerState.PLAYING
140  if play_status == PlayStatus.PAUSED:
141  return MediaPlayerState.PAUSED
142  if play_status == PlayStatus.TRANSITIONING:
143  return MediaPlayerState.BUFFERING
144  if play_status == PlayStatus.STOPPED:
145  return MediaPlayerState.IDLE
146  return MediaPlayerState.ON
147 
148  @property
149  def source(self):
150  """Get the currently selected source."""
151  return self._source_source.name
152 
153  @property
154  def source_list(self):
155  """Return a list of available input sources."""
156  return [x.name for x in self._sources_sources.values()]
157 
158  @property
159  def media_title(self):
160  """Title of current playing media."""
161  return self._source_source.song_name
162 
163  @property
164  def media_artist(self):
165  """Artist of current playing media, music track only."""
166  return self._source_source.artist_name
167 
168  @property
169  def media_album_name(self):
170  """Album name of current playing media, music track only."""
171  return self._source_source.album_name
172 
173  @property
174  def media_image_url(self):
175  """Image url of current playing media."""
176  return self._source_source.cover_art_url
177 
178  @property
179  def volume_level(self):
180  """Volume level of the media player (0..1).
181 
182  Value is returned based on a range (0..50).
183  Therefore float divide by 50 to get to the required range.
184  """
185  return self._zone_zone.volume / 50.0
186 
187  @command
188  async def async_turn_off(self) -> None:
189  """Turn off the zone."""
190  await self._zone_zone.zone_off()
191 
192  @command
193  async def async_turn_on(self) -> None:
194  """Turn on the zone."""
195  await self._zone_zone.zone_on()
196 
197  @command
198  async def async_set_volume_level(self, volume: float) -> None:
199  """Set the volume level."""
200  rvol = int(volume * 50.0)
201  await self._zone_zone.set_volume(str(rvol))
202 
203  @command
204  async def async_select_source(self, source: str) -> None:
205  """Select the source input for this zone."""
206  for source_id, src in self._sources_sources.items():
207  if src.name.lower() != source.lower():
208  continue
209  await self._zone_zone.select_source(source_id)
210  break
211 
212  @command
213  async def async_volume_up(self) -> None:
214  """Step the volume up."""
215  await self._zone_zone.volume_up()
216 
217  @command
218  async def async_volume_down(self) -> None:
219  """Step the volume down."""
220  await self._zone_zone.volume_down()
None __init__(self, Controller controller, int zone_id, dict[int, Source] sources)
None async_create_issue(HomeAssistant hass, str entry_id)
Definition: repairs.py:69
None async_setup_platform(HomeAssistant hass, ConfigType config, AddEntitiesCallback async_add_entities, DiscoveryInfoType|None discovery_info=None)
Definition: media_player.py:37
None async_setup_entry(HomeAssistant hass, RussoundConfigEntry entry, AddEntitiesCallback async_add_entities)
Definition: media_player.py:84