Home Assistant Unofficial Reference 2024.12.1
media_player.py
Go to the documentation of this file.
1 """Support for HomeKit Controller Televisions."""
2 
3 from __future__ import annotations
4 
5 import logging
6 
7 from aiohomekit.model.characteristics import (
8  CharacteristicsTypes,
9  CurrentMediaStateValues,
10  RemoteKeyValues,
11  TargetMediaStateValues,
12 )
13 from aiohomekit.model.services import Service, ServicesTypes
14 from aiohomekit.utils import clamp_enum_to_char
15 
17  MediaPlayerDeviceClass,
18  MediaPlayerEntity,
19  MediaPlayerEntityFeature,
20  MediaPlayerState,
21 )
22 from homeassistant.config_entries import ConfigEntry
23 from homeassistant.const import Platform
24 from homeassistant.core import HomeAssistant, callback
25 from homeassistant.helpers.entity_platform import AddEntitiesCallback
26 
27 from . import KNOWN_DEVICES
28 from .connection import HKDevice
29 from .entity import HomeKitEntity
30 
31 _LOGGER = logging.getLogger(__name__)
32 
33 
34 HK_TO_HA_STATE = {
35  CurrentMediaStateValues.PLAYING: MediaPlayerState.PLAYING,
36  CurrentMediaStateValues.PAUSED: MediaPlayerState.PAUSED,
37  CurrentMediaStateValues.STOPPED: MediaPlayerState.IDLE,
38 }
39 
40 
42  hass: HomeAssistant,
43  config_entry: ConfigEntry,
44  async_add_entities: AddEntitiesCallback,
45 ) -> None:
46  """Set up Homekit television."""
47  hkid: str = config_entry.data["AccessoryPairingID"]
48  conn: HKDevice = hass.data[KNOWN_DEVICES][hkid]
49 
50  @callback
51  def async_add_service(service: Service) -> bool:
52  if service.type != ServicesTypes.TELEVISION:
53  return False
54  info = {"aid": service.accessory.aid, "iid": service.iid}
55  entity = HomeKitTelevision(conn, info)
56  conn.async_migrate_unique_id(
57  entity.old_unique_id, entity.unique_id, Platform.MEDIA_PLAYER
58  )
59  async_add_entities([entity])
60  return True
61 
62  conn.add_listener(async_add_service)
63 
64 
66  """Representation of a HomeKit Controller Television."""
67 
68  _attr_device_class = MediaPlayerDeviceClass.TV
69 
70  def get_characteristic_types(self) -> list[str]:
71  """Define the homekit characteristics the entity cares about."""
72  return [
73  CharacteristicsTypes.ACTIVE,
74  CharacteristicsTypes.CURRENT_MEDIA_STATE,
75  CharacteristicsTypes.TARGET_MEDIA_STATE,
76  CharacteristicsTypes.REMOTE_KEY,
77  CharacteristicsTypes.ACTIVE_IDENTIFIER,
78  # Characterics that are on the linked INPUT_SOURCE services
79  CharacteristicsTypes.CONFIGURED_NAME,
80  CharacteristicsTypes.IDENTIFIER,
81  ]
82 
83  @property
84  def supported_features(self) -> MediaPlayerEntityFeature:
85  """Flag media player features that are supported."""
86  features = MediaPlayerEntityFeature(0)
87 
88  if self.serviceservice.has(CharacteristicsTypes.ACTIVE_IDENTIFIER):
89  features |= MediaPlayerEntityFeature.SELECT_SOURCE
90 
91  if self.serviceservice.has(CharacteristicsTypes.TARGET_MEDIA_STATE):
92  if TargetMediaStateValues.PAUSE in self.supported_media_statessupported_media_states:
93  features |= MediaPlayerEntityFeature.PAUSE
94 
95  if TargetMediaStateValues.PLAY in self.supported_media_statessupported_media_states:
96  features |= MediaPlayerEntityFeature.PLAY
97 
98  if TargetMediaStateValues.STOP in self.supported_media_statessupported_media_states:
99  features |= MediaPlayerEntityFeature.STOP
100 
101  if (
102  self.serviceservice.has(CharacteristicsTypes.REMOTE_KEY)
103  and RemoteKeyValues.PLAY_PAUSE in self.supported_remote_keyssupported_remote_keys
104  ):
105  features |= MediaPlayerEntityFeature.PAUSE | MediaPlayerEntityFeature.PLAY
106 
107  return features
108 
109  @property
110  def supported_media_states(self) -> set[TargetMediaStateValues]:
111  """Mediate state flags that are supported."""
112  if not self.serviceservice.has(CharacteristicsTypes.TARGET_MEDIA_STATE):
113  return set()
114 
115  return clamp_enum_to_char(
116  TargetMediaStateValues,
117  self.serviceservice[CharacteristicsTypes.TARGET_MEDIA_STATE],
118  )
119 
120  @property
121  def supported_remote_keys(self) -> set[int]:
122  """Remote key buttons that are supported."""
123  if not self.serviceservice.has(CharacteristicsTypes.REMOTE_KEY):
124  return set()
125 
126  return clamp_enum_to_char(
127  RemoteKeyValues, self.serviceservice[CharacteristicsTypes.REMOTE_KEY]
128  )
129 
130  @property
131  def source_list(self) -> list[str]:
132  """List of all input sources for this television."""
133  sources = []
134 
135  this_accessory = self._accessory_accessory.entity_map.aid(self._aid)
136  this_tv = this_accessory.services.iid(self._iid)
137 
138  input_sources = this_accessory.services.filter(
139  service_type=ServicesTypes.INPUT_SOURCE,
140  parent_service=this_tv,
141  )
142 
143  for input_source in input_sources:
144  char = input_source[CharacteristicsTypes.CONFIGURED_NAME]
145  sources.append(char.value)
146  return sources
147 
148  @property
149  def source(self) -> str | None:
150  """Name of the current input source."""
151  active_identifier = self.serviceservice.value(CharacteristicsTypes.ACTIVE_IDENTIFIER)
152  if not active_identifier:
153  return None
154 
155  this_accessory = self._accessory_accessory.entity_map.aid(self._aid)
156  this_tv = this_accessory.services.iid(self._iid)
157 
158  input_source = this_accessory.services.first(
159  service_type=ServicesTypes.INPUT_SOURCE,
160  characteristics={CharacteristicsTypes.IDENTIFIER: active_identifier},
161  parent_service=this_tv,
162  )
163  assert input_source
164  char = input_source[CharacteristicsTypes.CONFIGURED_NAME]
165  return char.value
166 
167  @property
168  def state(self) -> MediaPlayerState:
169  """State of the tv."""
170  active = self.serviceservice.value(CharacteristicsTypes.ACTIVE)
171  if not active:
172  return MediaPlayerState.OFF
173 
174  homekit_state = self.serviceservice.value(CharacteristicsTypes.CURRENT_MEDIA_STATE)
175  if homekit_state is not None:
176  return HK_TO_HA_STATE.get(homekit_state, MediaPlayerState.ON)
177 
178  return MediaPlayerState.ON
179 
180  async def async_media_play(self) -> None:
181  """Send play command."""
182  if self.statestatestatestatestatestate == MediaPlayerState.PLAYING:
183  _LOGGER.debug("Cannot play while already playing")
184  return
185 
186  if TargetMediaStateValues.PLAY in self.supported_media_statessupported_media_states:
187  await self.async_put_characteristicsasync_put_characteristics(
188  {CharacteristicsTypes.TARGET_MEDIA_STATE: TargetMediaStateValues.PLAY}
189  )
190  elif RemoteKeyValues.PLAY_PAUSE in self.supported_remote_keyssupported_remote_keys:
191  await self.async_put_characteristicsasync_put_characteristics(
192  {CharacteristicsTypes.REMOTE_KEY: RemoteKeyValues.PLAY_PAUSE}
193  )
194 
195  async def async_media_pause(self) -> None:
196  """Send pause command."""
197  if self.statestatestatestatestatestate == MediaPlayerState.PAUSED:
198  _LOGGER.debug("Cannot pause while already paused")
199  return
200 
201  if TargetMediaStateValues.PAUSE in self.supported_media_statessupported_media_states:
202  await self.async_put_characteristicsasync_put_characteristics(
203  {CharacteristicsTypes.TARGET_MEDIA_STATE: TargetMediaStateValues.PAUSE}
204  )
205  elif RemoteKeyValues.PLAY_PAUSE in self.supported_remote_keyssupported_remote_keys:
206  await self.async_put_characteristicsasync_put_characteristics(
207  {CharacteristicsTypes.REMOTE_KEY: RemoteKeyValues.PLAY_PAUSE}
208  )
209 
210  async def async_media_stop(self) -> None:
211  """Send stop command."""
212  if self.statestatestatestatestatestate == MediaPlayerState.IDLE:
213  _LOGGER.debug("Cannot stop when already idle")
214  return
215 
216  if TargetMediaStateValues.STOP in self.supported_media_statessupported_media_states:
217  await self.async_put_characteristicsasync_put_characteristics(
218  {CharacteristicsTypes.TARGET_MEDIA_STATE: TargetMediaStateValues.STOP}
219  )
220 
221  async def async_select_source(self, source: str) -> None:
222  """Switch to a different media source."""
223  this_accessory = self._accessory_accessory.entity_map.aid(self._aid)
224  this_tv = this_accessory.services.iid(self._iid)
225 
226  input_source = this_accessory.services.first(
227  service_type=ServicesTypes.INPUT_SOURCE,
228  characteristics={CharacteristicsTypes.CONFIGURED_NAME: source},
229  parent_service=this_tv,
230  )
231 
232  if not input_source:
233  raise ValueError(f"Could not find source {source}")
234 
235  identifier = input_source[CharacteristicsTypes.IDENTIFIER]
236 
237  await self.async_put_characteristicsasync_put_characteristics(
238  {CharacteristicsTypes.ACTIVE_IDENTIFIER: identifier.value}
239  )
None async_put_characteristics(self, dict[str, Any] characteristics)
Definition: entity.py:125
None async_setup_entry(HomeAssistant hass, ConfigEntry config_entry, AddEntitiesCallback async_add_entities)
Definition: media_player.py:45