Home Assistant Unofficial Reference 2024.12.1
intent.py
Go to the documentation of this file.
1 """Intents for the media_player integration."""
2 
3 from collections.abc import Iterable
4 from dataclasses import dataclass, field
5 import time
6 
7 import voluptuous as vol
8 
9 from homeassistant.const import (
10  SERVICE_MEDIA_NEXT_TRACK,
11  SERVICE_MEDIA_PAUSE,
12  SERVICE_MEDIA_PLAY,
13  SERVICE_MEDIA_PREVIOUS_TRACK,
14  SERVICE_VOLUME_SET,
15 )
16 from homeassistant.core import Context, HomeAssistant, State
17 from homeassistant.helpers import intent
18 
19 from . import ATTR_MEDIA_VOLUME_LEVEL, DOMAIN, MediaPlayerDeviceClass
20 from .const import MediaPlayerEntityFeature, MediaPlayerState
21 
22 INTENT_MEDIA_PAUSE = "HassMediaPause"
23 INTENT_MEDIA_UNPAUSE = "HassMediaUnpause"
24 INTENT_MEDIA_NEXT = "HassMediaNext"
25 INTENT_MEDIA_PREVIOUS = "HassMediaPrevious"
26 INTENT_SET_VOLUME = "HassSetVolume"
27 
28 
29 @dataclass
30 class LastPaused:
31  """Information about last media players that were paused by voice."""
32 
33  timestamp: float | None = None
34  context: Context | None = None
35  entity_ids: set[str] = field(default_factory=set)
36 
37  def clear(self) -> None:
38  """Clear timestamp and entities."""
39  self.timestamptimestamp = None
40  self.contextcontext = None
41  self.entity_idsentity_ids.clear()
42 
43  def update(self, context: Context | None, entity_ids: Iterable[str]) -> None:
44  """Update last paused group."""
45  self.contextcontext = context
46  self.entity_idsentity_ids = set(entity_ids)
47  if self.entity_idsentity_ids:
48  self.timestamptimestamp = time.time()
49 
50  def __bool__(self) -> bool:
51  """Return True if timestamp is set."""
52  return self.timestamptimestamp is not None
53 
54 
55 async def async_setup_intents(hass: HomeAssistant) -> None:
56  """Set up the media_player intents."""
57  last_paused = LastPaused()
58 
59  intent.async_register(hass, MediaUnpauseHandler(last_paused))
60  intent.async_register(hass, MediaPauseHandler(last_paused))
61  intent.async_register(
62  hass,
63  intent.ServiceIntentHandler(
64  INTENT_MEDIA_NEXT,
65  DOMAIN,
66  SERVICE_MEDIA_NEXT_TRACK,
67  required_domains={DOMAIN},
68  required_features=MediaPlayerEntityFeature.NEXT_TRACK,
69  required_states={MediaPlayerState.PLAYING},
70  description="Skips a media player to the next item",
71  platforms={DOMAIN},
72  device_classes={MediaPlayerDeviceClass},
73  ),
74  )
75  intent.async_register(
76  hass,
77  intent.ServiceIntentHandler(
78  INTENT_MEDIA_PREVIOUS,
79  DOMAIN,
80  SERVICE_MEDIA_PREVIOUS_TRACK,
81  required_domains={DOMAIN},
82  required_features=MediaPlayerEntityFeature.PREVIOUS_TRACK,
83  required_states={MediaPlayerState.PLAYING},
84  description="Replays the previous item for a media player",
85  platforms={DOMAIN},
86  device_classes={MediaPlayerDeviceClass},
87  ),
88  )
89  intent.async_register(
90  hass,
91  intent.ServiceIntentHandler(
92  INTENT_SET_VOLUME,
93  DOMAIN,
94  SERVICE_VOLUME_SET,
95  required_domains={DOMAIN},
96  required_states={MediaPlayerState.PLAYING},
97  required_features=MediaPlayerEntityFeature.VOLUME_SET,
98  required_slots={
99  ATTR_MEDIA_VOLUME_LEVEL: vol.All(
100  vol.Coerce(int), vol.Range(min=0, max=100), lambda val: val / 100
101  )
102  },
103  description="Sets the volume of a media player",
104  platforms={DOMAIN},
105  device_classes={MediaPlayerDeviceClass},
106  ),
107  )
108 
109 
110 class MediaPauseHandler(intent.ServiceIntentHandler):
111  """Handler for pause intent. Records last paused media players."""
112 
113  def __init__(self, last_paused: LastPaused) -> None:
114  """Initialize handler."""
115  super().__init__(
116  INTENT_MEDIA_PAUSE,
117  DOMAIN,
118  SERVICE_MEDIA_PAUSE,
119  required_domains={DOMAIN},
120  required_features=MediaPlayerEntityFeature.PAUSE,
121  required_states={MediaPlayerState.PLAYING},
122  description="Pauses a media player",
123  platforms={DOMAIN},
124  device_classes={MediaPlayerDeviceClass},
125  )
126  self.last_pausedlast_paused = last_paused
127 
129  self,
130  intent_obj: intent.Intent,
131  match_result: intent.MatchTargetsResult,
132  match_constraints: intent.MatchTargetsConstraints,
133  match_preferences: intent.MatchTargetsPreferences | None = None,
134  ) -> intent.IntentResponse:
135  """Record last paused media players."""
136  if match_result.is_match:
137  # Save entity ids of paused media players
138  self.last_pausedlast_paused.update(
139  intent_obj.context, (s.entity_id for s in match_result.states)
140  )
141 
142  return await super().async_handle_states(
143  intent_obj, match_result, match_constraints
144  )
145 
146 
147 class MediaUnpauseHandler(intent.ServiceIntentHandler):
148  """Handler for unpause/resume intent. Uses last paused media players."""
149 
150  def __init__(self, last_paused: LastPaused) -> None:
151  """Initialize handler."""
152  super().__init__(
153  INTENT_MEDIA_UNPAUSE,
154  DOMAIN,
155  SERVICE_MEDIA_PLAY,
156  required_domains={DOMAIN},
157  required_states={MediaPlayerState.PAUSED},
158  description="Resumes a media player",
159  platforms={DOMAIN},
160  device_classes={MediaPlayerDeviceClass},
161  )
162  self.last_pausedlast_paused = last_paused
163 
165  self,
166  intent_obj: intent.Intent,
167  match_result: intent.MatchTargetsResult,
168  match_constraints: intent.MatchTargetsConstraints,
169  match_preferences: intent.MatchTargetsPreferences | None = None,
170  ) -> intent.IntentResponse:
171  """Unpause last paused media players."""
172  if match_result.is_match and (not match_constraints.name) and self.last_pausedlast_paused:
173  assert self.last_pausedlast_paused.timestamp is not None
174 
175  # Check for a media player that was paused more recently than the
176  # ones by voice.
177  recent_state: State | None = None
178  for state in match_result.states:
179  if (state.last_changed_timestamp <= self.last_pausedlast_paused.timestamp) or (
180  state.context == self.last_pausedlast_paused.context
181  ):
182  continue
183 
184  if (recent_state is None) or (
185  state.last_changed_timestamp > recent_state.last_changed_timestamp
186  ):
187  recent_state = state
188 
189  if recent_state is not None:
190  # Resume the more recently paused media player (outside of voice).
191  match_result.states = [recent_state]
192  else:
193  # Resume only the previously paused media players if they are in the
194  # targeted set.
195  targeted_ids = {s.entity_id for s in match_result.states}
196  overlapping_ids = targeted_ids.intersection(self.last_pausedlast_paused.entity_ids)
197  if overlapping_ids:
198  match_result.states = [
199  s for s in match_result.states if s.entity_id in overlapping_ids
200  ]
201 
202  self.last_pausedlast_paused.clear()
203 
204  return await super().async_handle_states(
205  intent_obj, match_result, match_constraints
206  )
None update(self, Context|None context, Iterable[str] entity_ids)
Definition: intent.py:43
intent.IntentResponse async_handle_states(self, intent.Intent intent_obj, intent.MatchTargetsResult match_result, intent.MatchTargetsConstraints match_constraints, intent.MatchTargetsPreferences|None match_preferences=None)
Definition: intent.py:134
intent.IntentResponse async_handle_states(self, intent.Intent intent_obj, intent.MatchTargetsResult match_result, intent.MatchTargetsConstraints match_constraints, intent.MatchTargetsPreferences|None match_preferences=None)
Definition: intent.py:170
IssData update(pyiss.ISS iss)
Definition: __init__.py:33
None async_setup_intents(HomeAssistant hass)
Definition: intent.py:55