Home Assistant Unofficial Reference 2024.12.1
media_player.py
Go to the documentation of this file.
1 """Support for interfacing with Russound via RNET Protocol."""
2 
3 from __future__ import annotations
4 
5 import logging
6 import math
7 
8 from russound import russound
9 import voluptuous as vol
10 
12  PLATFORM_SCHEMA as MEDIA_PLAYER_PLATFORM_SCHEMA,
13  MediaPlayerEntity,
14  MediaPlayerEntityFeature,
15  MediaPlayerState,
16 )
17 from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT
18 from homeassistant.core import HomeAssistant
20 from homeassistant.helpers.entity_platform import AddEntitiesCallback
21 from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
22 
23 _LOGGER = logging.getLogger(__name__)
24 
25 CONF_ZONES = "zones"
26 CONF_SOURCES = "sources"
27 
28 
29 ZONE_SCHEMA = vol.Schema({vol.Required(CONF_NAME): cv.string})
30 
31 SOURCE_SCHEMA = vol.Schema({vol.Required(CONF_NAME): cv.string})
32 
33 PLATFORM_SCHEMA = MEDIA_PLAYER_PLATFORM_SCHEMA.extend(
34  {
35  vol.Required(CONF_HOST): cv.string,
36  vol.Required(CONF_NAME): cv.string,
37  vol.Required(CONF_PORT): cv.port,
38  vol.Required(CONF_ZONES): vol.Schema({cv.positive_int: ZONE_SCHEMA}),
39  vol.Required(CONF_SOURCES): vol.All(cv.ensure_list, [SOURCE_SCHEMA]),
40  }
41 )
42 
43 
45  hass: HomeAssistant,
46  config: ConfigType,
47  add_entities: AddEntitiesCallback,
48  discovery_info: DiscoveryInfoType | None = None,
49 ) -> None:
50  """Set up the Russound RNET platform."""
51  host = config.get(CONF_HOST)
52  port = config.get(CONF_PORT)
53 
54  if host is None or port is None:
55  _LOGGER.error("Invalid config. Expected %s and %s", CONF_HOST, CONF_PORT)
56  return
57 
58  russ = russound.Russound(host, port)
59  russ.connect()
60 
61  sources = [source["name"] for source in config[CONF_SOURCES]]
62 
63  if russ.is_connected():
64  for zone_id, extra in config[CONF_ZONES].items():
66  [RussoundRNETDevice(hass, russ, sources, zone_id, extra)], True
67  )
68  else:
69  _LOGGER.error("Not connected to %s:%s", host, port)
70 
71 
73  """Representation of a Russound RNET device."""
74 
75  _attr_supported_features = (
76  MediaPlayerEntityFeature.VOLUME_MUTE
77  | MediaPlayerEntityFeature.VOLUME_SET
78  | MediaPlayerEntityFeature.TURN_ON
79  | MediaPlayerEntityFeature.TURN_OFF
80  | MediaPlayerEntityFeature.SELECT_SOURCE
81  )
82 
83  def __init__(self, hass, russ, sources, zone_id, extra):
84  """Initialise the Russound RNET device."""
85  self._attr_name_attr_name = extra["name"]
86  self._russ_russ = russ
87  self._attr_source_list_attr_source_list = sources
88  # Each controller has a maximum of 6 zones, every increment of 6 zones
89  # maps to an additional controller for easier backward compatibility
90  self._controller_id_controller_id = str(math.ceil(zone_id / 6))
91  # Each zone resets to 1-6 per controller
92  self._zone_id_zone_id = (zone_id - 1) % 6 + 1
93 
94  def update(self) -> None:
95  """Retrieve latest state."""
96  # Updated this function to make a single call to get_zone_info, so that
97  # with a single call we can get On/Off, Volume and Source, reducing the
98  # amount of traffic and speeding up the update process.
99  try:
100  ret = self._russ_russ.get_zone_info(self._controller_id_controller_id, self._zone_id_zone_id, 4)
101  except BrokenPipeError:
102  _LOGGER.error("Broken Pipe Error, trying to reconnect to Russound RNET")
103  self._russ_russ.connect()
104  ret = self._russ_russ.get_zone_info(self._controller_id_controller_id, self._zone_id_zone_id, 4)
105 
106  _LOGGER.debug("ret= %s", ret)
107  if ret is not None:
108  _LOGGER.debug(
109  "Updating status for RNET zone %s on controller %s",
110  self._zone_id_zone_id,
111  self._controller_id_controller_id,
112  )
113  if ret[0] == 0:
114  self._attr_state_attr_state = MediaPlayerState.OFF
115  else:
116  self._attr_state_attr_state = MediaPlayerState.ON
117  self._attr_volume_level_attr_volume_level = ret[2] * 2 / 100.0
118  # Returns 0 based index for source.
119  index = ret[1]
120  # Possibility exists that user has defined list of all sources.
121  # If a source is set externally that is beyond the defined list then
122  # an exception will be thrown.
123  # In this case return and unknown source (None)
124  if self.source_listsource_list and 0 <= index < len(self.source_listsource_list):
125  self._attr_source_attr_source = self.source_listsource_list[index]
126  else:
127  _LOGGER.error("Could not update status for zone %s", self._zone_id_zone_id)
128 
129  def set_volume_level(self, volume: float) -> None:
130  """Set volume level. Volume has a range (0..1).
131 
132  Translate this to a range of (0..100) as expected
133  by _russ.set_volume()
134  """
135  self._russ_russ.set_volume(self._controller_id_controller_id, self._zone_id_zone_id, volume * 100)
136 
137  def turn_on(self) -> None:
138  """Turn the media player on."""
139  self._russ_russ.set_power(self._controller_id_controller_id, self._zone_id_zone_id, "1")
140 
141  def turn_off(self) -> None:
142  """Turn off media player."""
143  self._russ_russ.set_power(self._controller_id_controller_id, self._zone_id_zone_id, "0")
144 
145  def mute_volume(self, mute: bool) -> None:
146  """Send mute command."""
147  self._russ_russ.toggle_mute(self._controller_id_controller_id, self._zone_id_zone_id)
148 
149  def select_source(self, source: str) -> None:
150  """Set the input source."""
151  if self.source_listsource_list and source in self.source_listsource_list:
152  index = self.source_listsource_list.index(source)
153  # 0 based value for source
154  self._russ_russ.set_source(self._controller_id_controller_id, self._zone_id_zone_id, index)
def __init__(self, hass, russ, sources, zone_id, extra)
Definition: media_player.py:83
None add_entities(AsusWrtRouter router, AddEntitiesCallback async_add_entities, set[str] tracked)
None setup_platform(HomeAssistant hass, ConfigType config, AddEntitiesCallback add_entities, DiscoveryInfoType|None discovery_info=None)
Definition: media_player.py:49