Home Assistant Unofficial Reference 2024.12.1
media_player.py
Go to the documentation of this file.
1 """Support for Pioneer Network Receivers."""
2 
3 from __future__ import annotations
4 
5 import logging
6 import telnetlib # pylint: disable=deprecated-module
7 from typing import Final
8 
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, CONF_TIMEOUT
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_SOURCES = "sources"
26 
27 DEFAULT_NAME = "Pioneer AVR"
28 DEFAULT_PORT = 23 # telnet default. Some Pioneer AVRs use 8102
29 DEFAULT_TIMEOUT: Final = None
30 DEFAULT_SOURCES: dict[str, str] = {}
31 
32 
33 MAX_VOLUME = 185
34 MAX_SOURCE_NUMBERS = 60
35 
36 PLATFORM_SCHEMA = MEDIA_PLAYER_PLATFORM_SCHEMA.extend(
37  {
38  vol.Required(CONF_HOST): cv.string,
39  vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
40  vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
41  vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.socket_timeout,
42  vol.Optional(CONF_SOURCES, default=DEFAULT_SOURCES): {cv.string: cv.string},
43  }
44 )
45 
46 
48  hass: HomeAssistant,
49  config: ConfigType,
50  add_entities: AddEntitiesCallback,
51  discovery_info: DiscoveryInfoType | None = None,
52 ) -> None:
53  """Set up the Pioneer platform."""
54  pioneer = PioneerDevice(
55  config[CONF_NAME],
56  config[CONF_HOST],
57  config[CONF_PORT],
58  config[CONF_TIMEOUT],
59  config[CONF_SOURCES],
60  )
61 
62  if pioneer.update():
63  add_entities([pioneer])
64 
65 
67  """Representation of a Pioneer device."""
68 
69  _attr_supported_features = (
70  MediaPlayerEntityFeature.PAUSE
71  | MediaPlayerEntityFeature.VOLUME_SET
72  | MediaPlayerEntityFeature.VOLUME_STEP
73  | MediaPlayerEntityFeature.VOLUME_MUTE
74  | MediaPlayerEntityFeature.TURN_ON
75  | MediaPlayerEntityFeature.TURN_OFF
76  | MediaPlayerEntityFeature.SELECT_SOURCE
77  | MediaPlayerEntityFeature.PLAY
78  )
79 
80  def __init__(self, name, host, port, timeout, sources):
81  """Initialize the Pioneer device."""
82  self._name_name = name
83  self._host_host = host
84  self._port_port = port
85  self._timeout_timeout = timeout
86  self._pwstate_pwstate = "PWR1"
87  self._volume_volume = 0
88  self._muted_muted = False
89  self._selected_source_selected_source = ""
90  self._source_name_to_number_source_name_to_number = sources
91  self._source_number_to_name_source_number_to_name = {v: k for k, v in sources.items()}
92 
93  @classmethod
94  def telnet_request(cls, telnet, command, expected_prefix):
95  """Execute `command` and return the response."""
96  try:
97  telnet.write(command.encode("ASCII") + b"\r")
98  except telnetlib.socket.timeout:
99  _LOGGER.debug("Pioneer command %s timed out", command)
100  return None
101 
102  # The receiver will randomly send state change updates, make sure
103  # we get the response we are looking for
104  for _ in range(3):
105  result = telnet.read_until(b"\r\n", timeout=0.2).decode("ASCII").strip()
106  if result.startswith(expected_prefix):
107  return result
108 
109  return None
110 
111  def telnet_command(self, command):
112  """Establish a telnet connection and sends command."""
113  try:
114  try:
115  telnet = telnetlib.Telnet(self._host_host, self._port_port, self._timeout_timeout)
116  except OSError:
117  _LOGGER.warning("Pioneer %s refused connection", self._name_name)
118  return
119  telnet.write(command.encode("ASCII") + b"\r")
120  telnet.read_very_eager() # skip response
121  telnet.close()
122  except telnetlib.socket.timeout:
123  _LOGGER.debug("Pioneer %s command %s timed out", self._name_name, command)
124 
125  def update(self):
126  """Get the latest details from the device."""
127  try:
128  telnet = telnetlib.Telnet(self._host_host, self._port_port, self._timeout_timeout)
129  except OSError:
130  _LOGGER.warning("Pioneer %s refused connection", self._name_name)
131  return False
132 
133  pwstate = self.telnet_requesttelnet_request(telnet, "?P", "PWR")
134  if pwstate:
135  self._pwstate_pwstate = pwstate
136 
137  volume_str = self.telnet_requesttelnet_request(telnet, "?V", "VOL")
138  self._volume_volume = int(volume_str[3:]) / MAX_VOLUME if volume_str else None
139 
140  muted_value = self.telnet_requesttelnet_request(telnet, "?M", "MUT")
141  self._muted_muted = (muted_value == "MUT0") if muted_value else None
142 
143  # Build the source name dictionaries if necessary
144  if not self._source_name_to_number_source_name_to_number:
145  for i in range(MAX_SOURCE_NUMBERS):
146  result = self.telnet_requesttelnet_request(telnet, f"?RGB{str(i).zfill(2)}", "RGB")
147 
148  if not result:
149  continue
150 
151  source_name = result[6:]
152  source_number = str(i).zfill(2)
153 
154  self._source_name_to_number_source_name_to_number[source_name] = source_number
155  self._source_number_to_name_source_number_to_name[source_number] = source_name
156 
157  source_number = self.telnet_requesttelnet_request(telnet, "?F", "FN")
158 
159  if source_number:
160  self._selected_source_selected_source = self._source_number_to_name_source_number_to_name.get(source_number[2:])
161  else:
162  self._selected_source_selected_source = None
163 
164  telnet.close()
165  return True
166 
167  @property
168  def name(self):
169  """Return the name of the device."""
170  return self._name_name
171 
172  @property
173  def state(self) -> MediaPlayerState | None:
174  """Return the state of the device."""
175  if self._pwstate_pwstate == "PWR2":
176  return MediaPlayerState.OFF
177  if self._pwstate_pwstate == "PWR1":
178  return MediaPlayerState.OFF
179  if self._pwstate_pwstate == "PWR0":
180  return MediaPlayerState.ON
181 
182  return None
183 
184  @property
185  def volume_level(self):
186  """Volume level of the media player (0..1)."""
187  return self._volume_volume
188 
189  @property
190  def is_volume_muted(self):
191  """Boolean if volume is currently muted."""
192  return self._muted_muted
193 
194  @property
195  def source(self):
196  """Return the current input source."""
197  return self._selected_source_selected_source
198 
199  @property
200  def source_list(self):
201  """List of available input sources."""
202  return list(self._source_name_to_number_source_name_to_number)
203 
204  @property
205  def media_title(self):
206  """Title of current playing media."""
207  return self._selected_source_selected_source
208 
209  def turn_off(self) -> None:
210  """Turn off media player."""
211  self.telnet_commandtelnet_command("PF")
212 
213  def volume_up(self) -> None:
214  """Volume up media player."""
215  self.telnet_commandtelnet_command("VU")
216 
217  def volume_down(self) -> None:
218  """Volume down media player."""
219  self.telnet_commandtelnet_command("VD")
220 
221  def set_volume_level(self, volume: float) -> None:
222  """Set volume level, range 0..1."""
223  # 60dB max
224  self.telnet_commandtelnet_command(f"{round(volume * MAX_VOLUME):03}VL")
225 
226  def mute_volume(self, mute: bool) -> None:
227  """Mute (true) or unmute (false) media player."""
228  self.telnet_commandtelnet_command("MO" if mute else "MF")
229 
230  def turn_on(self) -> None:
231  """Turn the media player on."""
232  self.telnet_commandtelnet_command("PO")
233 
234  def select_source(self, source: str) -> None:
235  """Select input source."""
236  self.telnet_commandtelnet_command(f"{self._source_name_to_number.get(source)}FN")
def __init__(self, name, host, port, timeout, sources)
Definition: media_player.py:80
def telnet_request(cls, telnet, command, expected_prefix)
Definition: media_player.py:94
None add_entities(AsusWrtRouter router, AddEntitiesCallback async_add_entities, set[str] tracked)
web.Response get(self, web.Request request, str config_key)
Definition: view.py:88
None setup_platform(HomeAssistant hass, ConfigType config, AddEntitiesCallback add_entities, DiscoveryInfoType|None discovery_info=None)
Definition: media_player.py:52