Home Assistant Unofficial Reference 2024.12.1
media_player.py
Go to the documentation of this file.
1 """Support for controlling projector via the PJLink protocol."""
2 
3 from __future__ import annotations
4 
5 from pypjlink import MUTE_AUDIO, Projector
6 from pypjlink.projector import ProjectorError
7 import voluptuous as vol
8 
10  PLATFORM_SCHEMA as MEDIA_PLAYER_PLATFORM_SCHEMA,
11  MediaPlayerEntity,
12  MediaPlayerEntityFeature,
13  MediaPlayerState,
14 )
15 from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_PORT
16 from homeassistant.core import HomeAssistant
18 from homeassistant.helpers.entity_platform import AddEntitiesCallback
19 from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
20 
21 from .const import CONF_ENCODING, DEFAULT_ENCODING, DEFAULT_PORT, DOMAIN
22 
23 ERR_PROJECTOR_UNAVAILABLE = "projector unavailable"
24 
25 PLATFORM_SCHEMA = MEDIA_PLAYER_PLATFORM_SCHEMA.extend(
26  {
27  vol.Required(CONF_HOST): cv.string,
28  vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
29  vol.Optional(CONF_NAME): cv.string,
30  vol.Optional(CONF_ENCODING, default=DEFAULT_ENCODING): cv.string,
31  vol.Optional(CONF_PASSWORD): cv.string,
32  }
33 )
34 
35 
37  hass: HomeAssistant,
38  config: ConfigType,
39  add_entities: AddEntitiesCallback,
40  discovery_info: DiscoveryInfoType | None = None,
41 ) -> None:
42  """Set up the PJLink platform."""
43  host = config.get(CONF_HOST)
44  port = config.get(CONF_PORT)
45  name = config.get(CONF_NAME)
46  encoding = config.get(CONF_ENCODING)
47  password = config.get(CONF_PASSWORD)
48 
49  if DOMAIN not in hass.data:
50  hass.data[DOMAIN] = {}
51  hass_data = hass.data[DOMAIN]
52 
53  device_label = f"{host}:{port}"
54  if device_label in hass_data:
55  return
56 
57  device = PjLinkDevice(host, port, name, encoding, password)
58  hass_data[device_label] = device
59  add_entities([device], True)
60 
61 
62 def format_input_source(input_source_name, input_source_number):
63  """Format input source for display in UI."""
64  return f"{input_source_name} {input_source_number}"
65 
66 
68  """Representation of a PJLink device."""
69 
70  _attr_supported_features = (
71  MediaPlayerEntityFeature.VOLUME_MUTE
72  | MediaPlayerEntityFeature.TURN_ON
73  | MediaPlayerEntityFeature.TURN_OFF
74  | MediaPlayerEntityFeature.SELECT_SOURCE
75  )
76 
77  def __init__(self, host, port, name, encoding, password):
78  """Iinitialize the PJLink device."""
79  self._host_host = host
80  self._port_port = port
81  self._password_password = password
82  self._encoding_encoding = encoding
83  self._source_name_mapping_source_name_mapping = {}
84 
85  self._attr_name_attr_name = name
86  self._attr_is_volume_muted_attr_is_volume_muted = False
87  self._attr_state_attr_state = MediaPlayerState.OFF
88  self._attr_source_attr_source = None
89  self._attr_source_list_attr_source_list = []
90  self._attr_available_attr_available = False
91 
92  def _force_off(self):
93  self._attr_state_attr_state = MediaPlayerState.OFF
94  self._attr_is_volume_muted_attr_is_volume_muted = False
95  self._attr_source_attr_source = None
96 
97  def _setup_projector(self):
98  try:
99  with self.projectorprojector() as projector:
100  if not self._attr_name_attr_name:
101  self._attr_name_attr_name = projector.get_name()
102  inputs = projector.get_inputs()
103  except ProjectorError as err:
104  if str(err) == ERR_PROJECTOR_UNAVAILABLE:
105  return False
106  raise
107 
108  self._source_name_mapping_source_name_mapping = {format_input_source(*x): x for x in inputs}
109  self._attr_source_list_attr_source_list = sorted(self._source_name_mapping_source_name_mapping)
110  return True
111 
112  def projector(self):
113  """Create PJLink Projector instance."""
114 
115  try:
116  projector = Projector.from_address(self._host_host, self._port_port)
117  projector.authenticate(self._password_password)
118  except (TimeoutError, OSError) as err:
119  self._attr_available_attr_available = False
120  raise ProjectorError(ERR_PROJECTOR_UNAVAILABLE) from err
121 
122  return projector
123 
124  def update(self) -> None:
125  """Get the latest state from the device."""
126 
127  if not self._attr_available_attr_available:
128  self._attr_available_attr_available = self._setup_projector_setup_projector()
129 
130  if not self._attr_available_attr_available:
131  self._force_off_force_off()
132  return
133 
134  try:
135  with self.projectorprojector() as projector:
136  pwstate = projector.get_power()
137  if pwstate in ("on", "warm-up"):
138  self._attr_state_attr_state = MediaPlayerState.ON
139  self._attr_is_volume_muted_attr_is_volume_muted = projector.get_mute()[1]
140  self._attr_source_attr_source = format_input_source(*projector.get_input())
141  else:
142  self._force_off_force_off()
143  except KeyError as err:
144  if str(err) == "'OK'":
145  self._force_off_force_off()
146  else:
147  raise
148  except ProjectorError as err:
149  if str(err) == "unavailable time":
150  self._force_off_force_off()
151  elif str(err) == ERR_PROJECTOR_UNAVAILABLE:
152  self._attr_available_attr_available = False
153  else:
154  raise
155 
156  def turn_off(self) -> None:
157  """Turn projector off."""
158  with self.projectorprojector() as projector:
159  projector.set_power("off")
160 
161  def turn_on(self) -> None:
162  """Turn projector on."""
163  with self.projectorprojector() as projector:
164  projector.set_power("on")
165 
166  def mute_volume(self, mute: bool) -> None:
167  """Mute (true) of unmute (false) media player."""
168  with self.projectorprojector() as projector:
169  projector.set_mute(MUTE_AUDIO, mute)
170 
171  def select_source(self, source: str) -> None:
172  """Set the input source."""
173  source = self._source_name_mapping_source_name_mapping[source]
174  with self.projectorprojector() as projector:
175  projector.set_input(*source)
None add_entities(AsusWrtRouter router, AddEntitiesCallback async_add_entities, set[str] tracked)