Home Assistant Unofficial Reference 2024.12.1
media_player.py
Go to the documentation of this file.
1 """Support for interfacing with Monoprice 6 zone home audio controller."""
2 
3 import logging
4 
5 from serial import SerialException
6 
7 from homeassistant import core
9  MediaPlayerDeviceClass,
10  MediaPlayerEntity,
11  MediaPlayerEntityFeature,
12  MediaPlayerState,
13 )
14 from homeassistant.config_entries import ConfigEntry
15 from homeassistant.const import CONF_PORT
16 from homeassistant.core import HomeAssistant
17 from homeassistant.helpers import config_validation as cv, entity_platform, service
18 from homeassistant.helpers.device_registry import DeviceInfo
19 from homeassistant.helpers.entity_platform import AddEntitiesCallback
20 
21 from .const import (
22  CONF_SOURCES,
23  DOMAIN,
24  FIRST_RUN,
25  MONOPRICE_OBJECT,
26  SERVICE_RESTORE,
27  SERVICE_SNAPSHOT,
28 )
29 
30 _LOGGER = logging.getLogger(__name__)
31 
32 MAX_VOLUME = 38
33 PARALLEL_UPDATES = 1
34 
35 
36 @core.callback
38  sources_config = data[CONF_SOURCES]
39 
40  source_id_name = {int(index): name for index, name in sources_config.items()}
41 
42  source_name_id = {v: k for k, v in source_id_name.items()}
43 
44  source_names = sorted(source_name_id.keys(), key=lambda v: source_name_id[v])
45 
46  return [source_id_name, source_name_id, source_names]
47 
48 
49 @core.callback
50 def _get_sources(config_entry):
51  if CONF_SOURCES in config_entry.options:
52  data = config_entry.options
53  else:
54  data = config_entry.data
55  return _get_sources_from_dict(data)
56 
57 
59  hass: HomeAssistant,
60  config_entry: ConfigEntry,
61  async_add_entities: AddEntitiesCallback,
62 ) -> None:
63  """Set up the Monoprice 6-zone amplifier platform."""
64  port = config_entry.data[CONF_PORT]
65 
66  monoprice = hass.data[DOMAIN][config_entry.entry_id][MONOPRICE_OBJECT]
67 
68  sources = _get_sources(config_entry)
69 
70  entities = []
71  for i in range(1, 4):
72  for j in range(1, 7):
73  zone_id = (i * 10) + j
74  _LOGGER.debug("Adding zone %d for port %s", zone_id, port)
75  entities.append(
76  MonopriceZone(monoprice, sources, config_entry.entry_id, zone_id)
77  )
78 
79  # only call update before add if it's the first run so we can try to detect zones
80  first_run = hass.data[DOMAIN][config_entry.entry_id][FIRST_RUN]
81  async_add_entities(entities, first_run)
82 
83  platform = entity_platform.async_get_current_platform()
84 
85  def _call_service(entities, service_call):
86  for entity in entities:
87  if service_call.service == SERVICE_SNAPSHOT:
88  entity.snapshot()
89  elif service_call.service == SERVICE_RESTORE:
90  entity.restore()
91 
92  @service.verify_domain_control(hass, DOMAIN)
93  async def async_service_handle(service_call: core.ServiceCall) -> None:
94  """Handle for services."""
95  entities = await platform.async_extract_from_service(service_call)
96 
97  if not entities:
98  return
99 
100  hass.async_add_executor_job(_call_service, entities, service_call)
101 
102  hass.services.async_register(
103  DOMAIN,
104  SERVICE_SNAPSHOT,
105  async_service_handle,
106  schema=cv.make_entity_service_schema({}),
107  )
108 
109  hass.services.async_register(
110  DOMAIN,
111  SERVICE_RESTORE,
112  async_service_handle,
113  schema=cv.make_entity_service_schema({}),
114  )
115 
116 
118  """Representation of a Monoprice amplifier zone."""
119 
120  _attr_device_class = MediaPlayerDeviceClass.RECEIVER
121  _attr_supported_features = (
122  MediaPlayerEntityFeature.VOLUME_MUTE
123  | MediaPlayerEntityFeature.VOLUME_SET
124  | MediaPlayerEntityFeature.VOLUME_STEP
125  | MediaPlayerEntityFeature.TURN_ON
126  | MediaPlayerEntityFeature.TURN_OFF
127  | MediaPlayerEntityFeature.SELECT_SOURCE
128  )
129  _attr_has_entity_name = True
130  _attr_name = None
131 
132  def __init__(self, monoprice, sources, namespace, zone_id):
133  """Initialize new zone."""
134  self._monoprice_monoprice = monoprice
135  # dict source_id -> source name
136  self._source_id_name_source_id_name = sources[0]
137  # dict source name -> source_id
138  self._source_name_id_source_name_id = sources[1]
139  # ordered list of all source names
140  self._attr_source_list_attr_source_list = sources[2]
141  self._zone_id_zone_id = zone_id
142  self._attr_unique_id_attr_unique_id = f"{namespace}_{self._zone_id}"
143  self._attr_device_info_attr_device_info = DeviceInfo(
144  identifiers={(DOMAIN, self._attr_unique_id_attr_unique_id)},
145  manufacturer="Monoprice",
146  model="6-Zone Amplifier",
147  name=f"Zone {self._zone_id}",
148  )
149 
150  self._snapshot_snapshot = None
151  self._update_success_update_success = True
152 
153  def update(self) -> None:
154  """Retrieve latest state."""
155  try:
156  state = self._monoprice_monoprice.zone_status(self._zone_id_zone_id)
157  except SerialException:
158  self._update_success_update_success = False
159  _LOGGER.warning("Could not update zone %d", self._zone_id_zone_id)
160  return
161 
162  if not state:
163  self._update_success_update_success = False
164  return
165 
166  self._attr_state_attr_state = MediaPlayerState.ON if state.power else MediaPlayerState.OFF
167  self._attr_volume_level_attr_volume_level = state.volume / MAX_VOLUME
168  self._attr_is_volume_muted_attr_is_volume_muted = state.mute
169  idx = state.source
170  self._attr_source_attr_source = self._source_id_name_source_id_name.get(idx)
171 
172  @property
174  """Return if the entity should be enabled when first added to the entity registry."""
175  return self._zone_id_zone_id < 20 or self._update_success_update_success
176 
177  @property
178  def media_title(self):
179  """Return the current source as medial title."""
180  return self.sourcesource
181 
182  def snapshot(self):
183  """Save zone's current state."""
184  self._snapshot_snapshot = self._monoprice_monoprice.zone_status(self._zone_id_zone_id)
185 
186  def restore(self):
187  """Restore saved state."""
188  if self._snapshot_snapshot:
189  self._monoprice_monoprice.restore_zone(self._snapshot_snapshot)
190  self.schedule_update_ha_stateschedule_update_ha_state(True)
191 
192  def select_source(self, source: str) -> None:
193  """Set input source."""
194  if source not in self._source_name_id_source_name_id:
195  return
196  idx = self._source_name_id_source_name_id[source]
197  self._monoprice_monoprice.set_source(self._zone_id_zone_id, idx)
198 
199  def turn_on(self) -> None:
200  """Turn the media player on."""
201  self._monoprice_monoprice.set_power(self._zone_id_zone_id, True)
202 
203  def turn_off(self) -> None:
204  """Turn the media player off."""
205  self._monoprice_monoprice.set_power(self._zone_id_zone_id, False)
206 
207  def mute_volume(self, mute: bool) -> None:
208  """Mute (true) or unmute (false) media player."""
209  self._monoprice_monoprice.set_mute(self._zone_id_zone_id, mute)
210 
211  def set_volume_level(self, volume: float) -> None:
212  """Set volume level, range 0..1."""
213  self._monoprice_monoprice.set_volume(self._zone_id_zone_id, round(volume * MAX_VOLUME))
214 
215  def volume_up(self) -> None:
216  """Volume up the media player."""
217  if self.volume_levelvolume_level is None:
218  return
219  volume = round(self.volume_levelvolume_level * MAX_VOLUME)
220  self._monoprice_monoprice.set_volume(self._zone_id_zone_id, min(volume + 1, MAX_VOLUME))
221 
222  def volume_down(self) -> None:
223  """Volume down media player."""
224  if self.volume_levelvolume_level is None:
225  return
226  volume = round(self.volume_levelvolume_level * MAX_VOLUME)
227  self._monoprice_monoprice.set_volume(self._zone_id_zone_id, max(volume - 1, 0))
def __init__(self, monoprice, sources, namespace, zone_id)
None schedule_update_ha_state(self, bool force_refresh=False)
Definition: entity.py:1244
web.Response get(self, web.Request request, str config_key)
Definition: view.py:88
None async_setup_entry(HomeAssistant hass, ConfigEntry config_entry, AddEntitiesCallback async_add_entities)
Definition: media_player.py:62