Home Assistant Unofficial Reference 2024.12.1
media_player.py
Go to the documentation of this file.
1 """Support for the Mediaroom Set-up-box."""
2 
3 from __future__ import annotations
4 
5 import logging
6 from typing import Any
7 
8 from pymediaroom import (
9  COMMANDS,
10  PyMediaroomError,
11  Remote,
12  State,
13  install_mediaroom_protocol,
14 )
15 import voluptuous as vol
16 
18  PLATFORM_SCHEMA as MEDIA_PLAYER_PLATFORM_SCHEMA,
19  MediaPlayerEntity,
20  MediaPlayerEntityFeature,
21  MediaPlayerState,
22  MediaType,
23 )
24 from homeassistant.const import (
25  CONF_HOST,
26  CONF_NAME,
27  CONF_OPTIMISTIC,
28  CONF_TIMEOUT,
29  EVENT_HOMEASSISTANT_STOP,
30 )
31 from homeassistant.core import HomeAssistant, callback
33 from homeassistant.helpers.dispatcher import async_dispatcher_connect, dispatcher_send
34 from homeassistant.helpers.entity_platform import AddEntitiesCallback
35 from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
36 
37 _LOGGER = logging.getLogger(__name__)
38 
39 DATA_MEDIAROOM = "mediaroom_known_stb"
40 DEFAULT_NAME = "Mediaroom STB"
41 DEFAULT_TIMEOUT = 9
42 DISCOVERY_MEDIAROOM = "mediaroom_discovery_installed"
43 
44 MEDIA_TYPE_MEDIAROOM = "mediaroom"
45 
46 SIGNAL_STB_NOTIFY = "mediaroom_stb_discovered"
47 
48 
49 PLATFORM_SCHEMA = MEDIA_PLAYER_PLATFORM_SCHEMA.extend(
50  {
51  vol.Optional(CONF_HOST): cv.string,
52  vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
53  vol.Optional(CONF_OPTIMISTIC, default=False): cv.boolean,
54  vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int,
55  }
56 )
57 
58 
60  hass: HomeAssistant,
61  config: ConfigType,
62  async_add_entities: AddEntitiesCallback,
63  discovery_info: DiscoveryInfoType | None = None,
64 ) -> None:
65  """Set up the Mediaroom platform."""
66  if (known_hosts := hass.data.get(DATA_MEDIAROOM)) is None:
67  known_hosts = hass.data[DATA_MEDIAROOM] = []
68  if host := config.get(CONF_HOST):
70  [
72  host=host,
73  device_id=None,
74  optimistic=config[CONF_OPTIMISTIC],
75  timeout=config[CONF_TIMEOUT],
76  )
77  ]
78  )
79  known_hosts.append(host)
80 
81  _LOGGER.debug("Trying to discover Mediaroom STB")
82 
83  def callback_notify(notify):
84  """Process NOTIFY message from STB."""
85  if notify.ip_address in known_hosts:
86  dispatcher_send(hass, SIGNAL_STB_NOTIFY, notify)
87  return
88 
89  _LOGGER.debug("Discovered new stb %s", notify.ip_address)
90  known_hosts.append(notify.ip_address)
91  new_stb = MediaroomDevice(
92  host=notify.ip_address, device_id=notify.device_uuid, optimistic=False
93  )
94  async_add_entities([new_stb])
95 
96  if not config[CONF_OPTIMISTIC]:
97  already_installed = hass.data.get(DISCOVERY_MEDIAROOM)
98  if not already_installed:
99  hass.data[DISCOVERY_MEDIAROOM] = await install_mediaroom_protocol(
100  responses_callback=callback_notify
101  )
102 
103  @callback
104  def stop_discovery(event):
105  """Stop discovery of new mediaroom STB's."""
106  _LOGGER.debug("Stopping internal pymediaroom discovery")
107  hass.data[DISCOVERY_MEDIAROOM].close()
108 
109  hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, stop_discovery)
110 
111  _LOGGER.debug("Auto discovery installed")
112 
113 
115  """Representation of a Mediaroom set-up-box on the network."""
116 
117  _attr_media_content_type = MediaType.CHANNEL
118  _attr_should_poll = False
119  _attr_supported_features = (
120  MediaPlayerEntityFeature.PAUSE
121  | MediaPlayerEntityFeature.TURN_ON
122  | MediaPlayerEntityFeature.TURN_OFF
123  | MediaPlayerEntityFeature.VOLUME_STEP
124  | MediaPlayerEntityFeature.VOLUME_MUTE
125  | MediaPlayerEntityFeature.PLAY_MEDIA
126  | MediaPlayerEntityFeature.STOP
127  | MediaPlayerEntityFeature.NEXT_TRACK
128  | MediaPlayerEntityFeature.PREVIOUS_TRACK
129  | MediaPlayerEntityFeature.PLAY
130  )
131 
132  def set_state(self, mediaroom_state):
133  """Map pymediaroom state to HA state."""
134 
135  state_map = {
136  State.OFF: MediaPlayerState.OFF,
137  State.STANDBY: MediaPlayerState.STANDBY,
138  State.PLAYING_LIVE_TV: MediaPlayerState.PLAYING,
139  State.PLAYING_RECORDED_TV: MediaPlayerState.PLAYING,
140  State.PLAYING_TIMESHIFT_TV: MediaPlayerState.PLAYING,
141  State.STOPPED: MediaPlayerState.PAUSED,
142  State.UNKNOWN: None,
143  }
144 
145  self._attr_state_attr_state = state_map[mediaroom_state]
146 
147  def __init__(self, host, device_id, optimistic=False, timeout=DEFAULT_TIMEOUT):
148  """Initialize the device."""
149 
150  self.hosthost = host
151  self.stbstb = Remote(host)
152  _LOGGER.debug(
153  "Found STB at %s%s", host, " - I'm optimistic" if optimistic else ""
154  )
155  self._channel_channel = None
156  self._optimistic_optimistic = optimistic
157  self._attr_state_attr_state = (
158  MediaPlayerState.PLAYING if optimistic else MediaPlayerState.STANDBY
159  )
160  self._name_name = f"Mediaroom {device_id if device_id else host}"
161  self._available_available = True
162  if device_id:
163  self._unique_id_unique_id = device_id
164  else:
165  self._unique_id_unique_id = None
166 
167  @property
168  def available(self):
169  """Return True if entity is available."""
170  return self._available_available
171 
172  async def async_added_to_hass(self) -> None:
173  """Retrieve latest state."""
174 
175  async def async_notify_received(notify):
176  """Process STB state from NOTIFY message."""
177  stb_state = self.stbstb.notify_callback(notify)
178  # stb_state is None in case the notify is not from the current stb
179  if not stb_state:
180  return
181  self.set_stateset_state(stb_state)
182  _LOGGER.debug("STB(%s) is [%s]", self.hosthost, self.statestatestatestate)
183  self._available_available = True
184  self.async_write_ha_stateasync_write_ha_state()
185 
186  self.async_on_removeasync_on_remove(
188  self.hasshass, SIGNAL_STB_NOTIFY, async_notify_received
189  )
190  )
191 
192  async def async_play_media(
193  self, media_type: MediaType | str, media_id: str, **kwargs: Any
194  ) -> None:
195  """Play media."""
196 
197  _LOGGER.debug(
198  "STB(%s) Play media: %s (%s)", self.stbstb.stb_ip, media_id, media_type
199  )
200  command: str | int
201  if media_type == MediaType.CHANNEL:
202  if not media_id.isdigit():
203  _LOGGER.error("Invalid media_id %s: Must be a channel number", media_id)
204  return
205  command = int(media_id)
206  elif media_type == MEDIA_TYPE_MEDIAROOM:
207  if media_id not in COMMANDS:
208  _LOGGER.error("Invalid media_id %s: Must be a command", media_id)
209  return
210  command = media_id
211  else:
212  _LOGGER.error("Invalid media type %s", media_type)
213  return
214 
215  try:
216  await self.stbstb.send_cmd(command)
217  if self._optimistic_optimistic:
218  self._attr_state_attr_state = MediaPlayerState.PLAYING
219  self._available_available = True
220  except PyMediaroomError:
221  self._available_available = False
222  self.async_write_ha_stateasync_write_ha_state()
223 
224  @property
225  def unique_id(self):
226  """Return a unique ID."""
227  return self._unique_id_unique_id
228 
229  @property
230  def name(self):
231  """Return the name of the device."""
232  return self._name_name
233 
234  @property
235  def media_channel(self):
236  """Channel currently playing."""
237  return self._channel_channel
238 
239  async def async_turn_on(self) -> None:
240  """Turn on the receiver."""
241 
242  try:
243  self.set_stateset_state(await self.stbstb.turn_on())
244  if self._optimistic_optimistic:
245  self._attr_state_attr_state = MediaPlayerState.PLAYING
246  self._available_available = True
247  except PyMediaroomError:
248  self._available_available = False
249  self.async_write_ha_stateasync_write_ha_state()
250 
251  async def async_turn_off(self) -> None:
252  """Turn off the receiver."""
253 
254  try:
255  self.set_stateset_state(await self.stbstb.turn_off())
256  if self._optimistic_optimistic:
257  self._attr_state_attr_state = MediaPlayerState.STANDBY
258  self._available_available = True
259  except PyMediaroomError:
260  self._available_available = False
261  self.async_write_ha_stateasync_write_ha_state()
262 
263  async def async_media_play(self) -> None:
264  """Send play command."""
265 
266  try:
267  _LOGGER.debug("media_play()")
268  await self.stbstb.send_cmd("PlayPause")
269  if self._optimistic_optimistic:
270  self._attr_state_attr_state = MediaPlayerState.PLAYING
271  self._available_available = True
272  except PyMediaroomError:
273  self._available_available = False
274  self.async_write_ha_stateasync_write_ha_state()
275 
276  async def async_media_pause(self) -> None:
277  """Send pause command."""
278 
279  try:
280  await self.stbstb.send_cmd("PlayPause")
281  if self._optimistic_optimistic:
282  self._attr_state_attr_state = MediaPlayerState.PAUSED
283  self._available_available = True
284  except PyMediaroomError:
285  self._available_available = False
286  self.async_write_ha_stateasync_write_ha_state()
287 
288  async def async_media_stop(self) -> None:
289  """Send stop command."""
290 
291  try:
292  await self.stbstb.send_cmd("Stop")
293  if self._optimistic_optimistic:
294  self._attr_state_attr_state = MediaPlayerState.PAUSED
295  self._available_available = True
296  except PyMediaroomError:
297  self._available_available = False
298  self.async_write_ha_stateasync_write_ha_state()
299 
300  async def async_media_previous_track(self) -> None:
301  """Send Program Down command."""
302 
303  try:
304  await self.stbstb.send_cmd("ProgDown")
305  if self._optimistic_optimistic:
306  self._attr_state_attr_state = MediaPlayerState.PLAYING
307  self._available_available = True
308  except PyMediaroomError:
309  self._available_available = False
310  self.async_write_ha_stateasync_write_ha_state()
311 
312  async def async_media_next_track(self) -> None:
313  """Send Program Up command."""
314 
315  try:
316  await self.stbstb.send_cmd("ProgUp")
317  if self._optimistic_optimistic:
318  self._attr_state_attr_state = MediaPlayerState.PLAYING
319  self._available_available = True
320  except PyMediaroomError:
321  self._available_available = False
322  self.async_write_ha_stateasync_write_ha_state()
323 
324  async def async_volume_up(self) -> None:
325  """Send volume up command."""
326 
327  try:
328  await self.stbstb.send_cmd("VolUp")
329  self._available_available = True
330  except PyMediaroomError:
331  self._available_available = False
332  self.async_write_ha_stateasync_write_ha_state()
333 
334  async def async_volume_down(self) -> None:
335  """Send volume up command."""
336 
337  try:
338  await self.stbstb.send_cmd("VolDown")
339  except PyMediaroomError:
340  self._available_available = False
341  self.async_write_ha_stateasync_write_ha_state()
342 
343  async def async_mute_volume(self, mute: bool) -> None:
344  """Send mute command."""
345 
346  try:
347  await self.stbstb.send_cmd("Mute")
348  except PyMediaroomError:
349  self._available_available = False
350  self.async_write_ha_stateasync_write_ha_state()
None async_play_media(self, MediaType|str media_type, str media_id, **Any kwargs)
def __init__(self, host, device_id, optimistic=False, timeout=DEFAULT_TIMEOUT)
None async_on_remove(self, CALLBACK_TYPE func)
Definition: entity.py:1331
None async_setup_platform(HomeAssistant hass, ConfigType config, AddEntitiesCallback async_add_entities, DiscoveryInfoType|None discovery_info=None)
Definition: media_player.py:64
Callable[[], None] async_dispatcher_connect(HomeAssistant hass, str signal, Callable[..., Any] target)
Definition: dispatcher.py:103
None dispatcher_send(HomeAssistant hass, str signal, *Any args)
Definition: dispatcher.py:137