Home Assistant Unofficial Reference 2024.12.1
handlers.py
Go to the documentation of this file.
1 """Alexa message handlers."""
2 
3 from __future__ import annotations
4 
5 import asyncio
6 from collections.abc import Callable, Coroutine
7 import logging
8 import math
9 from typing import Any
10 
11 from homeassistant import core as ha
12 from homeassistant.components import (
13  alarm_control_panel,
14  button,
15  camera,
16  climate,
17  cover,
18  fan,
19  group,
20  humidifier,
21  input_button,
22  input_number,
23  light,
24  media_player,
25  number,
26  remote,
27  timer,
28  vacuum,
29  valve,
30  water_heater,
31 )
32 from homeassistant.const import (
33  ATTR_ENTITY_ID,
34  ATTR_ENTITY_PICTURE,
35  ATTR_SUPPORTED_FEATURES,
36  ATTR_TEMPERATURE,
37  SERVICE_ALARM_ARM_AWAY,
38  SERVICE_ALARM_ARM_HOME,
39  SERVICE_ALARM_ARM_NIGHT,
40  SERVICE_ALARM_DISARM,
41  SERVICE_LOCK,
42  SERVICE_MEDIA_NEXT_TRACK,
43  SERVICE_MEDIA_PAUSE,
44  SERVICE_MEDIA_PLAY,
45  SERVICE_MEDIA_PREVIOUS_TRACK,
46  SERVICE_MEDIA_STOP,
47  SERVICE_SET_COVER_POSITION,
48  SERVICE_SET_COVER_TILT_POSITION,
49  SERVICE_TURN_OFF,
50  SERVICE_TURN_ON,
51  SERVICE_UNLOCK,
52  SERVICE_VOLUME_DOWN,
53  SERVICE_VOLUME_MUTE,
54  SERVICE_VOLUME_SET,
55  SERVICE_VOLUME_UP,
56  UnitOfTemperature,
57 )
58 from homeassistant.helpers import network
59 from homeassistant.util import color as color_util, dt as dt_util
60 from homeassistant.util.decorator import Registry
61 from homeassistant.util.unit_conversion import TemperatureConverter
62 
63 from .config import AbstractConfig
64 from .const import (
65  API_TEMP_UNITS,
66  API_THERMOSTAT_MODES,
67  API_THERMOSTAT_MODES_CUSTOM,
68  API_THERMOSTAT_PRESETS,
69  DATE_FORMAT,
70  PRESET_MODE_NA,
71  Cause,
72  Inputs,
73 )
74 from .entities import async_get_entities
75 from .errors import (
76  AlexaInvalidDirectiveError,
77  AlexaInvalidValueError,
78  AlexaSecurityPanelAuthorizationRequired,
79  AlexaTempRangeError,
80  AlexaUnsupportedThermostatModeError,
81  AlexaUnsupportedThermostatTargetStateError,
82  AlexaVideoActionNotPermittedForContentError,
83 )
84 from .state_report import AlexaDirective, AlexaResponse, async_enable_proactive_mode
85 
86 _LOGGER = logging.getLogger(__name__)
87 DIRECTIVE_NOT_SUPPORTED = "Entity does not support directive"
88 
89 MIN_MAX_TEMP = {
90  climate.DOMAIN: {
91  "min_temp": climate.ATTR_MIN_TEMP,
92  "max_temp": climate.ATTR_MAX_TEMP,
93  },
94  water_heater.DOMAIN: {
95  "min_temp": water_heater.ATTR_MIN_TEMP,
96  "max_temp": water_heater.ATTR_MAX_TEMP,
97  },
98 }
99 
100 SERVICE_SET_TEMPERATURE = {
101  climate.DOMAIN: climate.SERVICE_SET_TEMPERATURE,
102  water_heater.DOMAIN: water_heater.SERVICE_SET_TEMPERATURE,
103 }
104 
105 HANDLERS: Registry[
106  tuple[str, str],
107  Callable[
108  [ha.HomeAssistant, AbstractConfig, AlexaDirective, ha.Context],
109  Coroutine[Any, Any, AlexaResponse],
110  ],
111 ] = Registry()
112 
113 
114 @HANDLERS.register(("Alexa.Discovery", "Discover"))
116  hass: ha.HomeAssistant,
117  config: AbstractConfig,
118  directive: AlexaDirective,
119  context: ha.Context,
120 ) -> AlexaResponse:
121  """Create a API formatted discovery response.
122 
123  Async friendly.
124  """
125  discovery_endpoints: list[dict[str, Any]] = []
126  for alexa_entity in async_get_entities(hass, config):
127  if not config.should_expose(alexa_entity.entity_id):
128  continue
129  try:
130  discovered_serialized_entity = alexa_entity.serialize_discovery()
131  except Exception:
132  _LOGGER.exception(
133  "Unable to serialize %s for discovery", alexa_entity.entity_id
134  )
135  else:
136  discovery_endpoints.append(discovered_serialized_entity)
137 
138  return directive.response(
139  name="Discover.Response",
140  namespace="Alexa.Discovery",
141  payload={"endpoints": discovery_endpoints},
142  )
143 
144 
145 @HANDLERS.register(("Alexa.Authorization", "AcceptGrant"))
147  hass: ha.HomeAssistant,
148  config: AbstractConfig,
149  directive: AlexaDirective,
150  context: ha.Context,
151 ) -> AlexaResponse:
152  """Create a API formatted AcceptGrant response.
153 
154  Async friendly.
155  """
156  auth_code: str = directive.payload["grant"]["code"]
157 
158  if config.supports_auth:
159  await config.async_accept_grant(auth_code)
160 
161  if config.should_report_state:
162  await async_enable_proactive_mode(hass, config)
163 
164  return directive.response(
165  name="AcceptGrant.Response", namespace="Alexa.Authorization", payload={}
166  )
167 
168 
169 @HANDLERS.register(("Alexa.PowerController", "TurnOn"))
171  hass: ha.HomeAssistant,
172  config: AbstractConfig,
173  directive: AlexaDirective,
174  context: ha.Context,
175 ) -> AlexaResponse:
176  """Process a turn on request."""
177  entity = directive.entity
178  if (domain := entity.domain) == group.DOMAIN:
179  domain = ha.DOMAIN
180 
181  service = SERVICE_TURN_ON
182  if domain == cover.DOMAIN:
183  service = cover.SERVICE_OPEN_COVER
184  elif domain == climate.DOMAIN:
185  service = climate.SERVICE_TURN_ON
186  elif domain == fan.DOMAIN:
187  service = fan.SERVICE_TURN_ON
188  elif domain == humidifier.DOMAIN:
189  service = humidifier.SERVICE_TURN_ON
190  elif domain == remote.DOMAIN:
191  service = remote.SERVICE_TURN_ON
192  elif domain == vacuum.DOMAIN:
193  supported = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
194  if (
195  not supported & vacuum.VacuumEntityFeature.TURN_ON
196  and supported & vacuum.VacuumEntityFeature.START
197  ):
198  service = vacuum.SERVICE_START
199  elif domain == timer.DOMAIN:
200  service = timer.SERVICE_START
201  elif domain == media_player.DOMAIN:
202  supported = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
203  power_features = (
204  media_player.MediaPlayerEntityFeature.TURN_ON
205  | media_player.MediaPlayerEntityFeature.TURN_OFF
206  )
207  if not supported & power_features:
208  service = media_player.SERVICE_MEDIA_PLAY
209 
210  await hass.services.async_call(
211  domain,
212  service,
213  {ATTR_ENTITY_ID: entity.entity_id},
214  blocking=False,
215  context=context,
216  )
217 
218  return directive.response()
219 
220 
221 @HANDLERS.register(("Alexa.PowerController", "TurnOff"))
223  hass: ha.HomeAssistant,
224  config: AbstractConfig,
225  directive: AlexaDirective,
226  context: ha.Context,
227 ) -> AlexaResponse:
228  """Process a turn off request."""
229  entity = directive.entity
230  domain = entity.domain
231  if entity.domain == group.DOMAIN:
232  domain = ha.DOMAIN
233 
234  service = SERVICE_TURN_OFF
235  if entity.domain == cover.DOMAIN:
236  service = cover.SERVICE_CLOSE_COVER
237  elif domain == climate.DOMAIN:
238  service = climate.SERVICE_TURN_OFF
239  elif domain == fan.DOMAIN:
240  service = fan.SERVICE_TURN_OFF
241  elif domain == remote.DOMAIN:
242  service = remote.SERVICE_TURN_OFF
243  elif domain == humidifier.DOMAIN:
244  service = humidifier.SERVICE_TURN_OFF
245  elif domain == vacuum.DOMAIN:
246  supported = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
247  if (
248  not supported & vacuum.VacuumEntityFeature.TURN_OFF
249  and supported & vacuum.VacuumEntityFeature.RETURN_HOME
250  ):
251  service = vacuum.SERVICE_RETURN_TO_BASE
252  elif domain == timer.DOMAIN:
253  service = timer.SERVICE_CANCEL
254  elif domain == media_player.DOMAIN:
255  supported = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
256  power_features = (
257  media_player.MediaPlayerEntityFeature.TURN_ON
258  | media_player.MediaPlayerEntityFeature.TURN_OFF
259  )
260  if not supported & power_features:
261  service = media_player.SERVICE_MEDIA_STOP
262 
263  await hass.services.async_call(
264  domain,
265  service,
266  {ATTR_ENTITY_ID: entity.entity_id},
267  blocking=False,
268  context=context,
269  )
270 
271  return directive.response()
272 
273 
274 @HANDLERS.register(("Alexa.BrightnessController", "SetBrightness"))
276  hass: ha.HomeAssistant,
277  config: AbstractConfig,
278  directive: AlexaDirective,
279  context: ha.Context,
280 ) -> AlexaResponse:
281  """Process a set brightness request."""
282  entity = directive.entity
283  brightness = int(directive.payload["brightness"])
284 
285  await hass.services.async_call(
286  entity.domain,
287  SERVICE_TURN_ON,
288  {ATTR_ENTITY_ID: entity.entity_id, light.ATTR_BRIGHTNESS_PCT: brightness},
289  blocking=False,
290  context=context,
291  )
292 
293  return directive.response()
294 
295 
296 @HANDLERS.register(("Alexa.BrightnessController", "AdjustBrightness"))
298  hass: ha.HomeAssistant,
299  config: AbstractConfig,
300  directive: AlexaDirective,
301  context: ha.Context,
302 ) -> AlexaResponse:
303  """Process an adjust brightness request."""
304  entity = directive.entity
305  brightness_delta = int(directive.payload["brightnessDelta"])
306 
307  # set brightness
308  await hass.services.async_call(
309  entity.domain,
310  SERVICE_TURN_ON,
311  {
312  ATTR_ENTITY_ID: entity.entity_id,
313  light.ATTR_BRIGHTNESS_STEP_PCT: brightness_delta,
314  },
315  blocking=False,
316  context=context,
317  )
318 
319  return directive.response()
320 
321 
322 @HANDLERS.register(("Alexa.ColorController", "SetColor"))
324  hass: ha.HomeAssistant,
325  config: AbstractConfig,
326  directive: AlexaDirective,
327  context: ha.Context,
328 ) -> AlexaResponse:
329  """Process a set color request."""
330  entity = directive.entity
331  rgb = color_util.color_hsb_to_RGB(
332  float(directive.payload["color"]["hue"]),
333  float(directive.payload["color"]["saturation"]),
334  float(directive.payload["color"]["brightness"]),
335  )
336 
337  await hass.services.async_call(
338  entity.domain,
339  SERVICE_TURN_ON,
340  {ATTR_ENTITY_ID: entity.entity_id, light.ATTR_RGB_COLOR: rgb},
341  blocking=False,
342  context=context,
343  )
344 
345  return directive.response()
346 
347 
348 @HANDLERS.register(("Alexa.ColorTemperatureController", "SetColorTemperature"))
350  hass: ha.HomeAssistant,
351  config: AbstractConfig,
352  directive: AlexaDirective,
353  context: ha.Context,
354 ) -> AlexaResponse:
355  """Process a set color temperature request."""
356  entity = directive.entity
357  kelvin = int(directive.payload["colorTemperatureInKelvin"])
358 
359  await hass.services.async_call(
360  entity.domain,
361  SERVICE_TURN_ON,
362  {ATTR_ENTITY_ID: entity.entity_id, light.ATTR_KELVIN: kelvin},
363  blocking=False,
364  context=context,
365  )
366 
367  return directive.response()
368 
369 
370 @HANDLERS.register(("Alexa.ColorTemperatureController", "DecreaseColorTemperature"))
372  hass: ha.HomeAssistant,
373  config: AbstractConfig,
374  directive: AlexaDirective,
375  context: ha.Context,
376 ) -> AlexaResponse:
377  """Process a decrease color temperature request."""
378  entity = directive.entity
379  current = int(entity.attributes[light.ATTR_COLOR_TEMP])
380  max_mireds = int(entity.attributes[light.ATTR_MAX_MIREDS])
381 
382  value = min(max_mireds, current + 50)
383  await hass.services.async_call(
384  entity.domain,
385  SERVICE_TURN_ON,
386  {ATTR_ENTITY_ID: entity.entity_id, light.ATTR_COLOR_TEMP: value},
387  blocking=False,
388  context=context,
389  )
390 
391  return directive.response()
392 
393 
394 @HANDLERS.register(("Alexa.ColorTemperatureController", "IncreaseColorTemperature"))
396  hass: ha.HomeAssistant,
397  config: AbstractConfig,
398  directive: AlexaDirective,
399  context: ha.Context,
400 ) -> AlexaResponse:
401  """Process an increase color temperature request."""
402  entity = directive.entity
403  current = int(entity.attributes[light.ATTR_COLOR_TEMP])
404  min_mireds = int(entity.attributes[light.ATTR_MIN_MIREDS])
405 
406  value = max(min_mireds, current - 50)
407  await hass.services.async_call(
408  entity.domain,
409  SERVICE_TURN_ON,
410  {ATTR_ENTITY_ID: entity.entity_id, light.ATTR_COLOR_TEMP: value},
411  blocking=False,
412  context=context,
413  )
414 
415  return directive.response()
416 
417 
418 @HANDLERS.register(("Alexa.SceneController", "Activate"))
420  hass: ha.HomeAssistant,
421  config: AbstractConfig,
422  directive: AlexaDirective,
423  context: ha.Context,
424 ) -> AlexaResponse:
425  """Process an activate request."""
426  entity = directive.entity
427  domain = entity.domain
428 
429  service = SERVICE_TURN_ON
430  if domain == button.DOMAIN:
431  service = button.SERVICE_PRESS
432  elif domain == input_button.DOMAIN:
433  service = input_button.SERVICE_PRESS
434 
435  await hass.services.async_call(
436  domain,
437  service,
438  {ATTR_ENTITY_ID: entity.entity_id},
439  blocking=False,
440  context=context,
441  )
442 
443  payload: dict[str, Any] = {
444  "cause": {"type": Cause.VOICE_INTERACTION},
445  "timestamp": dt_util.utcnow().strftime(DATE_FORMAT),
446  }
447 
448  return directive.response(
449  name="ActivationStarted", namespace="Alexa.SceneController", payload=payload
450  )
451 
452 
453 @HANDLERS.register(("Alexa.SceneController", "Deactivate"))
455  hass: ha.HomeAssistant,
456  config: AbstractConfig,
457  directive: AlexaDirective,
458  context: ha.Context,
459 ) -> AlexaResponse:
460  """Process a deactivate request."""
461  entity = directive.entity
462  domain = entity.domain
463 
464  await hass.services.async_call(
465  domain,
466  SERVICE_TURN_OFF,
467  {ATTR_ENTITY_ID: entity.entity_id},
468  blocking=False,
469  context=context,
470  )
471 
472  payload: dict[str, Any] = {
473  "cause": {"type": Cause.VOICE_INTERACTION},
474  "timestamp": dt_util.utcnow().strftime(DATE_FORMAT),
475  }
476 
477  return directive.response(
478  name="DeactivationStarted", namespace="Alexa.SceneController", payload=payload
479  )
480 
481 
482 @HANDLERS.register(("Alexa.LockController", "Lock"))
483 async def async_api_lock(
484  hass: ha.HomeAssistant,
485  config: AbstractConfig,
486  directive: AlexaDirective,
487  context: ha.Context,
488 ) -> AlexaResponse:
489  """Process a lock request."""
490  entity = directive.entity
491  await hass.services.async_call(
492  entity.domain,
493  SERVICE_LOCK,
494  {ATTR_ENTITY_ID: entity.entity_id},
495  blocking=False,
496  context=context,
497  )
498 
499  response = directive.response()
500  response.add_context_property(
501  {"name": "lockState", "namespace": "Alexa.LockController", "value": "LOCKED"}
502  )
503  return response
504 
505 
506 @HANDLERS.register(("Alexa.LockController", "Unlock"))
508  hass: ha.HomeAssistant,
509  config: AbstractConfig,
510  directive: AlexaDirective,
511  context: ha.Context,
512 ) -> AlexaResponse:
513  """Process an unlock request."""
514  if config.locale not in {
515  "ar-SA",
516  "de-DE",
517  "en-AU",
518  "en-CA",
519  "en-GB",
520  "en-IN",
521  "en-US",
522  "es-ES",
523  "es-MX",
524  "es-US",
525  "fr-CA",
526  "fr-FR",
527  "hi-IN",
528  "it-IT",
529  "ja-JP",
530  "pt-BR",
531  }:
532  msg = (
533  "The unlock directive is not supported for the following locales:"
534  f" {config.locale}"
535  )
536  raise AlexaInvalidDirectiveError(msg)
537 
538  entity = directive.entity
539  await hass.services.async_call(
540  entity.domain,
541  SERVICE_UNLOCK,
542  {ATTR_ENTITY_ID: entity.entity_id},
543  blocking=False,
544  context=context,
545  )
546 
547  response = directive.response()
548  response.add_context_property(
549  {"namespace": "Alexa.LockController", "name": "lockState", "value": "UNLOCKED"}
550  )
551 
552  return response
553 
554 
555 @HANDLERS.register(("Alexa.Speaker", "SetVolume"))
557  hass: ha.HomeAssistant,
558  config: AbstractConfig,
559  directive: AlexaDirective,
560  context: ha.Context,
561 ) -> AlexaResponse:
562  """Process a set volume request."""
563  volume = round(float(directive.payload["volume"] / 100), 2)
564  entity = directive.entity
565 
566  data: dict[str, Any] = {
567  ATTR_ENTITY_ID: entity.entity_id,
568  media_player.const.ATTR_MEDIA_VOLUME_LEVEL: volume,
569  }
570 
571  await hass.services.async_call(
572  entity.domain, SERVICE_VOLUME_SET, data, blocking=False, context=context
573  )
574 
575  return directive.response()
576 
577 
578 @HANDLERS.register(("Alexa.InputController", "SelectInput"))
580  hass: ha.HomeAssistant,
581  config: AbstractConfig,
582  directive: AlexaDirective,
583  context: ha.Context,
584 ) -> AlexaResponse:
585  """Process a set input request."""
586  media_input = directive.payload["input"]
587  entity = directive.entity
588 
589  # Attempt to map the ALL UPPERCASE payload name to a source.
590  # Strips trailing 1 to match single input devices.
591  source_list = entity.attributes.get(media_player.const.ATTR_INPUT_SOURCE_LIST) or []
592  for source in source_list:
593  formatted_source = (
594  source.lower().replace("-", "").replace("_", "").replace(" ", "")
595  )
596  media_input = media_input.lower().replace(" ", "")
597  if (
598  formatted_source in Inputs.VALID_SOURCE_NAME_MAP
599  and formatted_source == media_input
600  ) or (
601  media_input.endswith("1") and formatted_source == media_input.rstrip("1")
602  ):
603  media_input = source
604  break
605  else:
606  msg = (
607  f"failed to map input {media_input} to a media source on {entity.entity_id}"
608  )
609  raise AlexaInvalidValueError(msg)
610 
611  data: dict[str, Any] = {
612  ATTR_ENTITY_ID: entity.entity_id,
613  media_player.const.ATTR_INPUT_SOURCE: media_input,
614  }
615 
616  await hass.services.async_call(
617  entity.domain,
618  media_player.SERVICE_SELECT_SOURCE,
619  data,
620  blocking=False,
621  context=context,
622  )
623 
624  return directive.response()
625 
626 
627 @HANDLERS.register(("Alexa.Speaker", "AdjustVolume"))
629  hass: ha.HomeAssistant,
630  config: AbstractConfig,
631  directive: AlexaDirective,
632  context: ha.Context,
633 ) -> AlexaResponse:
634  """Process an adjust volume request."""
635  volume_delta = int(directive.payload["volume"])
636 
637  entity = directive.entity
638  current_level = entity.attributes[media_player.const.ATTR_MEDIA_VOLUME_LEVEL]
639 
640  # read current state
641  try:
642  current = math.floor(int(current_level * 100))
643  except ZeroDivisionError:
644  current = 0
645 
646  volume = float(max(0, volume_delta + current) / 100)
647 
648  data: dict[str, Any] = {
649  ATTR_ENTITY_ID: entity.entity_id,
650  media_player.const.ATTR_MEDIA_VOLUME_LEVEL: volume,
651  }
652 
653  await hass.services.async_call(
654  entity.domain, SERVICE_VOLUME_SET, data, blocking=False, context=context
655  )
656 
657  return directive.response()
658 
659 
660 @HANDLERS.register(("Alexa.StepSpeaker", "AdjustVolume"))
662  hass: ha.HomeAssistant,
663  config: AbstractConfig,
664  directive: AlexaDirective,
665  context: ha.Context,
666 ) -> AlexaResponse:
667  """Process an adjust volume step request."""
668  # media_player volume up/down service does not support specifying steps
669  # each component handles it differently e.g. via config.
670  # This workaround will simply call the volume up/Volume down the amount of
671  # steps asked for. When no steps are called in the request, Alexa sends
672  # a default of 10 steps which for most purposes is too high. The default
673  # is set 1 in this case.
674  entity = directive.entity
675  volume_int = int(directive.payload["volumeSteps"])
676  is_default = bool(directive.payload["volumeStepsDefault"])
677  default_steps = 1
678 
679  if volume_int < 0:
680  service_volume = SERVICE_VOLUME_DOWN
681  if is_default:
682  volume_int = -default_steps
683  else:
684  service_volume = SERVICE_VOLUME_UP
685  if is_default:
686  volume_int = default_steps
687 
688  data: dict[str, Any] = {ATTR_ENTITY_ID: entity.entity_id}
689 
690  for _ in range(abs(volume_int)):
691  await hass.services.async_call(
692  entity.domain, service_volume, data, blocking=False, context=context
693  )
694 
695  return directive.response()
696 
697 
698 @HANDLERS.register(("Alexa.StepSpeaker", "SetMute"))
699 @HANDLERS.register(("Alexa.Speaker", "SetMute"))
701  hass: ha.HomeAssistant,
702  config: AbstractConfig,
703  directive: AlexaDirective,
704  context: ha.Context,
705 ) -> AlexaResponse:
706  """Process a set mute request."""
707  mute = bool(directive.payload["mute"])
708  entity = directive.entity
709  data: dict[str, Any] = {
710  ATTR_ENTITY_ID: entity.entity_id,
711  media_player.const.ATTR_MEDIA_VOLUME_MUTED: mute,
712  }
713 
714  await hass.services.async_call(
715  entity.domain, SERVICE_VOLUME_MUTE, data, blocking=False, context=context
716  )
717 
718  return directive.response()
719 
720 
721 @HANDLERS.register(("Alexa.PlaybackController", "Play"))
722 async def async_api_play(
723  hass: ha.HomeAssistant,
724  config: AbstractConfig,
725  directive: AlexaDirective,
726  context: ha.Context,
727 ) -> AlexaResponse:
728  """Process a play request."""
729  entity = directive.entity
730  data: dict[str, Any] = {ATTR_ENTITY_ID: entity.entity_id}
731 
732  await hass.services.async_call(
733  entity.domain, SERVICE_MEDIA_PLAY, data, blocking=False, context=context
734  )
735 
736  return directive.response()
737 
738 
739 @HANDLERS.register(("Alexa.PlaybackController", "Pause"))
740 async def async_api_pause(
741  hass: ha.HomeAssistant,
742  config: AbstractConfig,
743  directive: AlexaDirective,
744  context: ha.Context,
745 ) -> AlexaResponse:
746  """Process a pause request."""
747  entity = directive.entity
748  data: dict[str, Any] = {ATTR_ENTITY_ID: entity.entity_id}
749 
750  await hass.services.async_call(
751  entity.domain, SERVICE_MEDIA_PAUSE, data, blocking=False, context=context
752  )
753 
754  return directive.response()
755 
756 
757 @HANDLERS.register(("Alexa.PlaybackController", "Stop"))
758 async def async_api_stop(
759  hass: ha.HomeAssistant,
760  config: AbstractConfig,
761  directive: AlexaDirective,
762  context: ha.Context,
763 ) -> AlexaResponse:
764  """Process a stop request."""
765  entity = directive.entity
766  data: dict[str, Any] = {ATTR_ENTITY_ID: entity.entity_id}
767 
768  if entity.domain == cover.DOMAIN:
769  supported: int = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
770  feature_services: dict[int, str] = {
771  cover.CoverEntityFeature.STOP.value: cover.SERVICE_STOP_COVER,
772  cover.CoverEntityFeature.STOP_TILT.value: cover.SERVICE_STOP_COVER_TILT,
773  }
774  await asyncio.gather(
775  *(
776  hass.services.async_call(
777  entity.domain, service, data, blocking=False, context=context
778  )
779  for feature, service in feature_services.items()
780  if feature & supported
781  )
782  )
783  else:
784  await hass.services.async_call(
785  entity.domain, SERVICE_MEDIA_STOP, data, blocking=False, context=context
786  )
787 
788  return directive.response()
789 
790 
791 @HANDLERS.register(("Alexa.PlaybackController", "Next"))
792 async def async_api_next(
793  hass: ha.HomeAssistant,
794  config: AbstractConfig,
795  directive: AlexaDirective,
796  context: ha.Context,
797 ) -> AlexaResponse:
798  """Process a next request."""
799  entity = directive.entity
800  data: dict[str, Any] = {ATTR_ENTITY_ID: entity.entity_id}
801 
802  await hass.services.async_call(
803  entity.domain, SERVICE_MEDIA_NEXT_TRACK, data, blocking=False, context=context
804  )
805 
806  return directive.response()
807 
808 
809 @HANDLERS.register(("Alexa.PlaybackController", "Previous"))
811  hass: ha.HomeAssistant,
812  config: AbstractConfig,
813  directive: AlexaDirective,
814  context: ha.Context,
815 ) -> AlexaResponse:
816  """Process a previous request."""
817  entity = directive.entity
818  data: dict[str, Any] = {ATTR_ENTITY_ID: entity.entity_id}
819 
820  await hass.services.async_call(
821  entity.domain,
822  SERVICE_MEDIA_PREVIOUS_TRACK,
823  data,
824  blocking=False,
825  context=context,
826  )
827 
828  return directive.response()
829 
830 
832  hass: ha.HomeAssistant, temp_obj: dict[str, Any], interval: bool = False
833 ) -> float:
834  """Get temperature from Temperature object in requested unit."""
835  to_unit = hass.config.units.temperature_unit
836  from_unit = UnitOfTemperature.CELSIUS
837  temp = float(temp_obj["value"])
838 
839  if temp_obj["scale"] == "FAHRENHEIT":
840  from_unit = UnitOfTemperature.FAHRENHEIT
841  elif temp_obj["scale"] == "KELVIN" and not interval:
842  # convert to Celsius if absolute temperature
843  temp -= 273.15
844 
845  if interval:
846  return TemperatureConverter.convert_interval(temp, from_unit, to_unit)
847  return TemperatureConverter.convert(temp, from_unit, to_unit)
848 
849 
850 @HANDLERS.register(("Alexa.ThermostatController", "SetTargetTemperature"))
852  hass: ha.HomeAssistant,
853  config: AbstractConfig,
854  directive: AlexaDirective,
855  context: ha.Context,
856 ) -> AlexaResponse:
857  """Process a set target temperature request."""
858  entity = directive.entity
859  domain = entity.domain
860 
861  min_temp = entity.attributes[MIN_MAX_TEMP[domain]["min_temp"]]
862  max_temp = entity.attributes["max_temp"]
863  unit = hass.config.units.temperature_unit
864 
865  data: dict[str, Any] = {ATTR_ENTITY_ID: entity.entity_id}
866 
867  payload = directive.payload
868  response = directive.response()
869  if "targetSetpoint" in payload:
870  temp = temperature_from_object(hass, payload["targetSetpoint"])
871  if temp < min_temp or temp > max_temp:
872  raise AlexaTempRangeError(hass, temp, min_temp, max_temp)
873  data[ATTR_TEMPERATURE] = temp
874  response.add_context_property(
875  {
876  "name": "targetSetpoint",
877  "namespace": "Alexa.ThermostatController",
878  "value": {"value": temp, "scale": API_TEMP_UNITS[unit]},
879  }
880  )
881  if "lowerSetpoint" in payload:
882  temp_low = temperature_from_object(hass, payload["lowerSetpoint"])
883  if temp_low < min_temp or temp_low > max_temp:
884  raise AlexaTempRangeError(hass, temp_low, min_temp, max_temp)
885  data[climate.ATTR_TARGET_TEMP_LOW] = temp_low
886  response.add_context_property(
887  {
888  "name": "lowerSetpoint",
889  "namespace": "Alexa.ThermostatController",
890  "value": {"value": temp_low, "scale": API_TEMP_UNITS[unit]},
891  }
892  )
893  if "upperSetpoint" in payload:
894  temp_high = temperature_from_object(hass, payload["upperSetpoint"])
895  if temp_high < min_temp or temp_high > max_temp:
896  raise AlexaTempRangeError(hass, temp_high, min_temp, max_temp)
897  data[climate.ATTR_TARGET_TEMP_HIGH] = temp_high
898  response.add_context_property(
899  {
900  "name": "upperSetpoint",
901  "namespace": "Alexa.ThermostatController",
902  "value": {"value": temp_high, "scale": API_TEMP_UNITS[unit]},
903  }
904  )
905 
906  service = SERVICE_SET_TEMPERATURE[domain]
907 
908  await hass.services.async_call(
909  entity.domain,
910  service,
911  data,
912  blocking=False,
913  context=context,
914  )
915 
916  return response
917 
918 
919 @HANDLERS.register(("Alexa.ThermostatController", "AdjustTargetTemperature"))
921  hass: ha.HomeAssistant,
922  config: AbstractConfig,
923  directive: AlexaDirective,
924  context: ha.Context,
925 ) -> AlexaResponse:
926  """Process an adjust target temperature request for climates and water heaters."""
927  data: dict[str, Any]
928  entity = directive.entity
929  domain = entity.domain
930  min_temp = entity.attributes[MIN_MAX_TEMP[domain]["min_temp"]]
931  max_temp = entity.attributes[MIN_MAX_TEMP[domain]["max_temp"]]
932  unit = hass.config.units.temperature_unit
933 
934  temp_delta = temperature_from_object(
935  hass, directive.payload["targetSetpointDelta"], interval=True
936  )
937 
938  response = directive.response()
939 
940  current_target_temp_high = entity.attributes.get(climate.ATTR_TARGET_TEMP_HIGH)
941  current_target_temp_low = entity.attributes.get(climate.ATTR_TARGET_TEMP_LOW)
942  if current_target_temp_high is not None and current_target_temp_low is not None:
943  target_temp_high = float(current_target_temp_high) + temp_delta
944  if target_temp_high < min_temp or target_temp_high > max_temp:
945  raise AlexaTempRangeError(hass, target_temp_high, min_temp, max_temp)
946 
947  target_temp_low = float(current_target_temp_low) + temp_delta
948  if target_temp_low < min_temp or target_temp_low > max_temp:
949  raise AlexaTempRangeError(hass, target_temp_low, min_temp, max_temp)
950 
951  data = {
952  ATTR_ENTITY_ID: entity.entity_id,
953  climate.ATTR_TARGET_TEMP_HIGH: target_temp_high,
954  climate.ATTR_TARGET_TEMP_LOW: target_temp_low,
955  }
956 
957  response.add_context_property(
958  {
959  "name": "upperSetpoint",
960  "namespace": "Alexa.ThermostatController",
961  "value": {"value": target_temp_high, "scale": API_TEMP_UNITS[unit]},
962  }
963  )
964  response.add_context_property(
965  {
966  "name": "lowerSetpoint",
967  "namespace": "Alexa.ThermostatController",
968  "value": {"value": target_temp_low, "scale": API_TEMP_UNITS[unit]},
969  }
970  )
971  else:
972  current_target_temp: str | None = entity.attributes.get(ATTR_TEMPERATURE)
973  if current_target_temp is None:
975  "The current target temperature is not set, "
976  "cannot adjust target temperature"
977  )
978  target_temp = float(current_target_temp) + temp_delta
979 
980  if target_temp < min_temp or target_temp > max_temp:
981  raise AlexaTempRangeError(hass, target_temp, min_temp, max_temp)
982 
983  data = {ATTR_ENTITY_ID: entity.entity_id, ATTR_TEMPERATURE: target_temp}
984  response.add_context_property(
985  {
986  "name": "targetSetpoint",
987  "namespace": "Alexa.ThermostatController",
988  "value": {"value": target_temp, "scale": API_TEMP_UNITS[unit]},
989  }
990  )
991 
992  service = SERVICE_SET_TEMPERATURE[domain]
993 
994  await hass.services.async_call(
995  entity.domain,
996  service,
997  data,
998  blocking=False,
999  context=context,
1000  )
1001 
1002  return response
1003 
1004 
1005 @HANDLERS.register(("Alexa.ThermostatController", "SetThermostatMode"))
1007  hass: ha.HomeAssistant,
1008  config: AbstractConfig,
1009  directive: AlexaDirective,
1010  context: ha.Context,
1011 ) -> AlexaResponse:
1012  """Process a set thermostat mode request."""
1013  operation_list: list[str]
1014 
1015  entity = directive.entity
1016  mode = directive.payload["thermostatMode"]
1017  mode = mode if isinstance(mode, str) else mode["value"]
1018 
1019  data: dict[str, Any] = {ATTR_ENTITY_ID: entity.entity_id}
1020 
1021  ha_preset = next((k for k, v in API_THERMOSTAT_PRESETS.items() if v == mode), None)
1022 
1023  if ha_preset:
1024  presets = entity.attributes.get(climate.ATTR_PRESET_MODES) or []
1025 
1026  if ha_preset not in presets:
1027  msg = f"The requested thermostat mode {ha_preset} is not supported"
1029 
1030  service = climate.SERVICE_SET_PRESET_MODE
1031  data[climate.ATTR_PRESET_MODE] = ha_preset
1032 
1033  elif mode == "CUSTOM":
1034  operation_list = entity.attributes.get(climate.ATTR_HVAC_MODES) or []
1035  custom_mode = directive.payload["thermostatMode"]["customName"]
1036  custom_mode = next(
1037  (k for k, v in API_THERMOSTAT_MODES_CUSTOM.items() if v == custom_mode),
1038  None,
1039  )
1040  if custom_mode not in operation_list:
1041  msg = (
1042  f"The requested thermostat mode {mode}: {custom_mode} is not supported"
1043  )
1045 
1046  service = climate.SERVICE_SET_HVAC_MODE
1047  data[climate.ATTR_HVAC_MODE] = custom_mode
1048 
1049  else:
1050  operation_list = entity.attributes.get(climate.ATTR_HVAC_MODES) or []
1051  ha_modes: dict[str, str] = {
1052  k: v for k, v in API_THERMOSTAT_MODES.items() if v == mode
1053  }
1054  ha_mode: str | None = next(
1055  iter(set(ha_modes).intersection(operation_list)), None
1056  )
1057  if ha_mode not in operation_list:
1058  msg = f"The requested thermostat mode {mode} is not supported"
1060 
1061  service = climate.SERVICE_SET_HVAC_MODE
1062  data[climate.ATTR_HVAC_MODE] = ha_mode
1063 
1064  response = directive.response()
1065  await hass.services.async_call(
1066  climate.DOMAIN, service, data, blocking=False, context=context
1067  )
1068  response.add_context_property(
1069  {
1070  "name": "thermostatMode",
1071  "namespace": "Alexa.ThermostatController",
1072  "value": mode,
1073  }
1074  )
1075 
1076  return response
1077 
1078 
1079 @HANDLERS.register(("Alexa", "ReportState"))
1081  hass: ha.HomeAssistant,
1082  config: AbstractConfig,
1083  directive: AlexaDirective,
1084  context: ha.Context,
1085 ) -> AlexaResponse:
1086  """Process a ReportState request."""
1087  return directive.response(name="StateReport")
1088 
1089 
1090 @HANDLERS.register(("Alexa.SecurityPanelController", "Arm"))
1091 async def async_api_arm(
1092  hass: ha.HomeAssistant,
1093  config: AbstractConfig,
1094  directive: AlexaDirective,
1095  context: ha.Context,
1096 ) -> AlexaResponse:
1097  """Process a Security Panel Arm request."""
1098  entity = directive.entity
1099  service = None
1100  arm_state = directive.payload["armState"]
1101  data: dict[str, Any] = {ATTR_ENTITY_ID: entity.entity_id}
1102 
1103  # Per Alexa Documentation: users are not allowed to switch from armed_away
1104  # directly to another armed state without first disarming the system.
1105  # https://developer.amazon.com/en-US/docs/alexa/device-apis/alexa-securitypanelcontroller.html#arming
1106  if (
1107  entity.state == alarm_control_panel.AlarmControlPanelState.ARMED_AWAY
1108  and arm_state != "ARMED_AWAY"
1109  ):
1110  msg = "You must disarm the system before you can set the requested arm state."
1112 
1113  if arm_state == "ARMED_AWAY":
1114  service = SERVICE_ALARM_ARM_AWAY
1115  elif arm_state == "ARMED_NIGHT":
1116  service = SERVICE_ALARM_ARM_NIGHT
1117  elif arm_state == "ARMED_STAY":
1118  service = SERVICE_ALARM_ARM_HOME
1119  else:
1120  raise AlexaInvalidDirectiveError(DIRECTIVE_NOT_SUPPORTED)
1121 
1122  await hass.services.async_call(
1123  entity.domain, service, data, blocking=False, context=context
1124  )
1125 
1126  # return 0 until alarm integration supports an exit delay
1127  payload: dict[str, Any] = {"exitDelayInSeconds": 0}
1128 
1129  response = directive.response(
1130  name="Arm.Response", namespace="Alexa.SecurityPanelController", payload=payload
1131  )
1132 
1133  response.add_context_property(
1134  {
1135  "name": "armState",
1136  "namespace": "Alexa.SecurityPanelController",
1137  "value": arm_state,
1138  }
1139  )
1140 
1141  return response
1142 
1143 
1144 @HANDLERS.register(("Alexa.SecurityPanelController", "Disarm"))
1146  hass: ha.HomeAssistant,
1147  config: AbstractConfig,
1148  directive: AlexaDirective,
1149  context: ha.Context,
1150 ) -> AlexaResponse:
1151  """Process a Security Panel Disarm request."""
1152  entity = directive.entity
1153  data: dict[str, Any] = {ATTR_ENTITY_ID: entity.entity_id}
1154  response = directive.response()
1155 
1156  # Per Alexa Documentation: If you receive a Disarm directive, and the
1157  # system is already disarmed, respond with a success response,
1158  # not an error response.
1159  if entity.state == alarm_control_panel.AlarmControlPanelState.DISARMED:
1160  return response
1161 
1162  payload = directive.payload
1163  if "authorization" in payload:
1164  value = payload["authorization"]["value"]
1165  if payload["authorization"]["type"] == "FOUR_DIGIT_PIN":
1166  data["code"] = value
1167 
1168  await hass.services.async_call(
1169  entity.domain, SERVICE_ALARM_DISARM, data, blocking=True, context=context
1170  )
1171 
1172  response.add_context_property(
1173  {
1174  "name": "armState",
1175  "namespace": "Alexa.SecurityPanelController",
1176  "value": "DISARMED",
1177  }
1178  )
1179 
1180  return response
1181 
1182 
1183 @HANDLERS.register(("Alexa.ModeController", "SetMode"))
1185  hass: ha.HomeAssistant,
1186  config: AbstractConfig,
1187  directive: AlexaDirective,
1188  context: ha.Context,
1189 ) -> AlexaResponse:
1190  """Process a SetMode directive."""
1191  entity = directive.entity
1192  instance = directive.instance
1193  domain = entity.domain
1194  service = None
1195  data: dict[str, Any] = {ATTR_ENTITY_ID: entity.entity_id}
1196  mode = directive.payload["mode"]
1197 
1198  # Fan Direction
1199  if instance == f"{fan.DOMAIN}.{fan.ATTR_DIRECTION}":
1200  direction = mode.split(".")[1]
1201  if direction in (fan.DIRECTION_REVERSE, fan.DIRECTION_FORWARD):
1202  service = fan.SERVICE_SET_DIRECTION
1203  data[fan.ATTR_DIRECTION] = direction
1204 
1205  # Fan preset_mode
1206  elif instance == f"{fan.DOMAIN}.{fan.ATTR_PRESET_MODE}":
1207  preset_mode = mode.split(".")[1]
1208  preset_modes: list[str] | None = entity.attributes.get(fan.ATTR_PRESET_MODES)
1209  if (
1210  preset_mode != PRESET_MODE_NA
1211  and preset_modes
1212  and preset_mode in preset_modes
1213  ):
1214  service = fan.SERVICE_SET_PRESET_MODE
1215  data[fan.ATTR_PRESET_MODE] = preset_mode
1216  else:
1217  msg = f"Entity '{entity.entity_id}' does not support Preset '{preset_mode}'"
1218  raise AlexaInvalidValueError(msg)
1219 
1220  # Humidifier mode
1221  elif instance == f"{humidifier.DOMAIN}.{humidifier.ATTR_MODE}":
1222  mode = mode.split(".")[1]
1223  modes: list[str] | None = entity.attributes.get(humidifier.ATTR_AVAILABLE_MODES)
1224  if mode != PRESET_MODE_NA and modes and mode in modes:
1225  service = humidifier.SERVICE_SET_MODE
1226  data[humidifier.ATTR_MODE] = mode
1227  else:
1228  msg = f"Entity '{entity.entity_id}' does not support Mode '{mode}'"
1229  raise AlexaInvalidValueError(msg)
1230 
1231  # Remote Activity
1232  elif instance == f"{remote.DOMAIN}.{remote.ATTR_ACTIVITY}":
1233  activity = mode.split(".")[1]
1234  activities: list[str] | None = entity.attributes.get(remote.ATTR_ACTIVITY_LIST)
1235  if activity != PRESET_MODE_NA and activities and activity in activities:
1236  service = remote.SERVICE_TURN_ON
1237  data[remote.ATTR_ACTIVITY] = activity
1238  else:
1239  msg = f"Entity '{entity.entity_id}' does not support Mode '{mode}'"
1240  raise AlexaInvalidValueError(msg)
1241 
1242  # Water heater operation mode
1243  elif instance == f"{water_heater.DOMAIN}.{water_heater.ATTR_OPERATION_MODE}":
1244  operation_mode = mode.split(".")[1]
1245  operation_modes: list[str] | None = entity.attributes.get(
1246  water_heater.ATTR_OPERATION_LIST
1247  )
1248  if (
1249  operation_mode != PRESET_MODE_NA
1250  and operation_modes
1251  and operation_mode in operation_modes
1252  ):
1253  service = water_heater.SERVICE_SET_OPERATION_MODE
1254  data[water_heater.ATTR_OPERATION_MODE] = operation_mode
1255  else:
1256  msg = f"Entity '{entity.entity_id}' does not support Operation mode '{operation_mode}'"
1257  raise AlexaInvalidValueError(msg)
1258 
1259  # Cover Position
1260  elif instance == f"{cover.DOMAIN}.{cover.ATTR_POSITION}":
1261  position = mode.split(".")[1]
1262 
1263  if position == cover.STATE_CLOSED:
1264  service = cover.SERVICE_CLOSE_COVER
1265  elif position == cover.STATE_OPEN:
1266  service = cover.SERVICE_OPEN_COVER
1267  elif position == "custom":
1268  service = cover.SERVICE_STOP_COVER
1269 
1270  # Valve position state
1271  elif instance == f"{valve.DOMAIN}.state":
1272  position = mode.split(".")[1]
1273 
1274  if position == valve.STATE_CLOSED:
1275  service = valve.SERVICE_CLOSE_VALVE
1276  elif position == valve.STATE_OPEN:
1277  service = valve.SERVICE_OPEN_VALVE
1278 
1279  if not service:
1280  raise AlexaInvalidDirectiveError(DIRECTIVE_NOT_SUPPORTED)
1281 
1282  await hass.services.async_call(
1283  domain, service, data, blocking=False, context=context
1284  )
1285 
1286  response = directive.response()
1287  response.add_context_property(
1288  {
1289  "namespace": "Alexa.ModeController",
1290  "instance": instance,
1291  "name": "mode",
1292  "value": mode,
1293  }
1294  )
1295 
1296  return response
1297 
1298 
1299 @HANDLERS.register(("Alexa.ModeController", "AdjustMode"))
1301  hass: ha.HomeAssistant,
1302  config: AbstractConfig,
1303  directive: AlexaDirective,
1304  context: ha.Context,
1305 ) -> AlexaResponse:
1306  """Process a AdjustMode request.
1307 
1308  Requires capabilityResources supportedModes to be ordered.
1309  Only supportedModes with ordered=True support the adjustMode directive.
1310  """
1311 
1312  # Currently no supportedModes are configured with ordered=True
1313  # to support this request.
1314  raise AlexaInvalidDirectiveError(DIRECTIVE_NOT_SUPPORTED)
1315 
1316 
1317 @HANDLERS.register(("Alexa.ToggleController", "TurnOn"))
1319  hass: ha.HomeAssistant,
1320  config: AbstractConfig,
1321  directive: AlexaDirective,
1322  context: ha.Context,
1323 ) -> AlexaResponse:
1324  """Process a toggle on request."""
1325  entity = directive.entity
1326  instance = directive.instance
1327  domain = entity.domain
1328 
1329  data: dict[str, Any]
1330 
1331  # Fan Oscillating
1332  if instance == f"{fan.DOMAIN}.{fan.ATTR_OSCILLATING}":
1333  service = fan.SERVICE_OSCILLATE
1334  data = {
1335  ATTR_ENTITY_ID: entity.entity_id,
1336  fan.ATTR_OSCILLATING: True,
1337  }
1338  elif instance == f"{valve.DOMAIN}.stop":
1339  service = valve.SERVICE_STOP_VALVE
1340  data = {
1341  ATTR_ENTITY_ID: entity.entity_id,
1342  }
1343  else:
1344  raise AlexaInvalidDirectiveError(DIRECTIVE_NOT_SUPPORTED)
1345 
1346  await hass.services.async_call(
1347  domain, service, data, blocking=False, context=context
1348  )
1349 
1350  response = directive.response()
1351  response.add_context_property(
1352  {
1353  "namespace": "Alexa.ToggleController",
1354  "instance": instance,
1355  "name": "toggleState",
1356  "value": "ON",
1357  }
1358  )
1359 
1360  return response
1361 
1362 
1363 @HANDLERS.register(("Alexa.ToggleController", "TurnOff"))
1365  hass: ha.HomeAssistant,
1366  config: AbstractConfig,
1367  directive: AlexaDirective,
1368  context: ha.Context,
1369 ) -> AlexaResponse:
1370  """Process a toggle off request."""
1371  entity = directive.entity
1372  instance = directive.instance
1373  domain = entity.domain
1374 
1375  # Fan Oscillating
1376  if instance != f"{fan.DOMAIN}.{fan.ATTR_OSCILLATING}":
1377  raise AlexaInvalidDirectiveError(DIRECTIVE_NOT_SUPPORTED)
1378 
1379  service = fan.SERVICE_OSCILLATE
1380  data: dict[str, Any] = {
1381  ATTR_ENTITY_ID: entity.entity_id,
1382  fan.ATTR_OSCILLATING: False,
1383  }
1384 
1385  await hass.services.async_call(
1386  domain, service, data, blocking=False, context=context
1387  )
1388 
1389  response = directive.response()
1390  response.add_context_property(
1391  {
1392  "namespace": "Alexa.ToggleController",
1393  "instance": instance,
1394  "name": "toggleState",
1395  "value": "OFF",
1396  }
1397  )
1398 
1399  return response
1400 
1401 
1402 @HANDLERS.register(("Alexa.RangeController", "SetRangeValue"))
1404  hass: ha.HomeAssistant,
1405  config: AbstractConfig,
1406  directive: AlexaDirective,
1407  context: ha.Context,
1408 ) -> AlexaResponse:
1409  """Process a next request."""
1410  entity = directive.entity
1411  instance = directive.instance
1412  domain = entity.domain
1413  service = None
1414  data: dict[str, Any] = {ATTR_ENTITY_ID: entity.entity_id}
1415  range_value = directive.payload["rangeValue"]
1416  supported = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
1417 
1418  # Cover Position
1419  if instance == f"{cover.DOMAIN}.{cover.ATTR_POSITION}":
1420  range_value = int(range_value)
1421  if supported & cover.CoverEntityFeature.CLOSE and range_value == 0:
1422  service = cover.SERVICE_CLOSE_COVER
1423  elif supported & cover.CoverEntityFeature.OPEN and range_value == 100:
1424  service = cover.SERVICE_OPEN_COVER
1425  else:
1426  service = cover.SERVICE_SET_COVER_POSITION
1427  data[cover.ATTR_POSITION] = range_value
1428 
1429  # Cover Tilt
1430  elif instance == f"{cover.DOMAIN}.tilt":
1431  range_value = int(range_value)
1432  if supported & cover.CoverEntityFeature.CLOSE_TILT and range_value == 0:
1433  service = cover.SERVICE_CLOSE_COVER_TILT
1434  elif supported & cover.CoverEntityFeature.OPEN_TILT and range_value == 100:
1435  service = cover.SERVICE_OPEN_COVER_TILT
1436  else:
1437  service = cover.SERVICE_SET_COVER_TILT_POSITION
1438  data[cover.ATTR_TILT_POSITION] = range_value
1439 
1440  # Fan Speed
1441  elif instance == f"{fan.DOMAIN}.{fan.ATTR_PERCENTAGE}":
1442  range_value = int(range_value)
1443  if range_value == 0:
1444  service = fan.SERVICE_TURN_OFF
1445  elif supported & fan.FanEntityFeature.SET_SPEED:
1446  service = fan.SERVICE_SET_PERCENTAGE
1447  data[fan.ATTR_PERCENTAGE] = range_value
1448  else:
1449  service = fan.SERVICE_TURN_ON
1450 
1451  # Humidifier target humidity
1452  elif instance == f"{humidifier.DOMAIN}.{humidifier.ATTR_HUMIDITY}":
1453  range_value = int(range_value)
1454  service = humidifier.SERVICE_SET_HUMIDITY
1455  data[humidifier.ATTR_HUMIDITY] = range_value
1456 
1457  # Input Number Value
1458  elif instance == f"{input_number.DOMAIN}.{input_number.ATTR_VALUE}":
1459  range_value = float(range_value)
1460  service = input_number.SERVICE_SET_VALUE
1461  min_value = float(entity.attributes[input_number.ATTR_MIN])
1462  max_value = float(entity.attributes[input_number.ATTR_MAX])
1463  data[input_number.ATTR_VALUE] = min(max_value, max(min_value, range_value))
1464 
1465  # Input Number Value
1466  elif instance == f"{number.DOMAIN}.{number.ATTR_VALUE}":
1467  range_value = float(range_value)
1468  service = number.SERVICE_SET_VALUE
1469  min_value = float(entity.attributes[number.ATTR_MIN])
1470  max_value = float(entity.attributes[number.ATTR_MAX])
1471  data[number.ATTR_VALUE] = min(max_value, max(min_value, range_value))
1472 
1473  # Vacuum Fan Speed
1474  elif instance == f"{vacuum.DOMAIN}.{vacuum.ATTR_FAN_SPEED}":
1475  service = vacuum.SERVICE_SET_FAN_SPEED
1476  speed_list = entity.attributes[vacuum.ATTR_FAN_SPEED_LIST]
1477  speed = next(
1478  (v for i, v in enumerate(speed_list) if i == int(range_value)), None
1479  )
1480 
1481  if not speed:
1482  msg = "Entity does not support value"
1483  raise AlexaInvalidValueError(msg)
1484 
1485  data[vacuum.ATTR_FAN_SPEED] = speed
1486 
1487  # Valve Position
1488  elif instance == f"{valve.DOMAIN}.{valve.ATTR_POSITION}":
1489  range_value = int(range_value)
1490  if supported & valve.ValveEntityFeature.CLOSE and range_value == 0:
1491  service = valve.SERVICE_CLOSE_VALVE
1492  elif supported & valve.ValveEntityFeature.OPEN and range_value == 100:
1493  service = valve.SERVICE_OPEN_VALVE
1494  else:
1495  service = valve.SERVICE_SET_VALVE_POSITION
1496  data[valve.ATTR_POSITION] = range_value
1497 
1498  else:
1499  raise AlexaInvalidDirectiveError(DIRECTIVE_NOT_SUPPORTED)
1500 
1501  await hass.services.async_call(
1502  domain, service, data, blocking=False, context=context
1503  )
1504 
1505  response = directive.response()
1506  response.add_context_property(
1507  {
1508  "namespace": "Alexa.RangeController",
1509  "instance": instance,
1510  "name": "rangeValue",
1511  "value": range_value,
1512  }
1513  )
1514 
1515  return response
1516 
1517 
1518 @HANDLERS.register(("Alexa.RangeController", "AdjustRangeValue"))
1520  hass: ha.HomeAssistant,
1521  config: AbstractConfig,
1522  directive: AlexaDirective,
1523  context: ha.Context,
1524 ) -> AlexaResponse:
1525  """Process a next request."""
1526  entity = directive.entity
1527  instance = directive.instance
1528  domain = entity.domain
1529  service = None
1530  data: dict[str, Any] = {ATTR_ENTITY_ID: entity.entity_id}
1531  range_delta = directive.payload["rangeValueDelta"]
1532  range_delta_default = bool(directive.payload["rangeValueDeltaDefault"])
1533  response_value: int | None = 0
1534 
1535  # Cover Position
1536  if instance == f"{cover.DOMAIN}.{cover.ATTR_POSITION}":
1537  range_delta = int(range_delta * 20) if range_delta_default else int(range_delta)
1538  service = SERVICE_SET_COVER_POSITION
1539  if not (current := entity.attributes.get(cover.ATTR_CURRENT_POSITION)):
1540  msg = f"Unable to determine {entity.entity_id} current position"
1541  raise AlexaInvalidValueError(msg)
1542  position = response_value = min(100, max(0, range_delta + current))
1543  if position == 100:
1544  service = cover.SERVICE_OPEN_COVER
1545  elif position == 0:
1546  service = cover.SERVICE_CLOSE_COVER
1547  else:
1548  data[cover.ATTR_POSITION] = position
1549 
1550  # Cover Tilt
1551  elif instance == f"{cover.DOMAIN}.tilt":
1552  range_delta = int(range_delta * 20) if range_delta_default else int(range_delta)
1553  service = SERVICE_SET_COVER_TILT_POSITION
1554  current = entity.attributes.get(cover.ATTR_TILT_POSITION)
1555  if not current:
1556  msg = f"Unable to determine {entity.entity_id} current tilt position"
1557  raise AlexaInvalidValueError(msg)
1558  tilt_position = response_value = min(100, max(0, range_delta + current))
1559  if tilt_position == 100:
1560  service = cover.SERVICE_OPEN_COVER_TILT
1561  elif tilt_position == 0:
1562  service = cover.SERVICE_CLOSE_COVER_TILT
1563  else:
1564  data[cover.ATTR_TILT_POSITION] = tilt_position
1565 
1566  # Fan speed percentage
1567  elif instance == f"{fan.DOMAIN}.{fan.ATTR_PERCENTAGE}":
1568  percentage_step = entity.attributes.get(fan.ATTR_PERCENTAGE_STEP) or 20
1569  range_delta = (
1570  int(range_delta * percentage_step)
1571  if range_delta_default
1572  else int(range_delta)
1573  )
1574  service = fan.SERVICE_SET_PERCENTAGE
1575  if not (current := entity.attributes.get(fan.ATTR_PERCENTAGE)):
1576  msg = f"Unable to determine {entity.entity_id} current fan speed"
1577  raise AlexaInvalidValueError(msg)
1578  percentage = response_value = min(100, max(0, range_delta + current))
1579  if percentage:
1580  data[fan.ATTR_PERCENTAGE] = percentage
1581  else:
1582  service = fan.SERVICE_TURN_OFF
1583 
1584  # Humidifier target humidity
1585  elif instance == f"{humidifier.DOMAIN}.{humidifier.ATTR_HUMIDITY}":
1586  percentage_step = 5
1587  range_delta = (
1588  int(range_delta * percentage_step)
1589  if range_delta_default
1590  else int(range_delta)
1591  )
1592  service = humidifier.SERVICE_SET_HUMIDITY
1593  if not (current := entity.attributes.get(humidifier.ATTR_HUMIDITY)):
1594  msg = f"Unable to determine {entity.entity_id} current target humidity"
1595  raise AlexaInvalidValueError(msg)
1596  min_value = entity.attributes.get(humidifier.ATTR_MIN_HUMIDITY, 10)
1597  max_value = entity.attributes.get(humidifier.ATTR_MAX_HUMIDITY, 90)
1598  percentage = response_value = min(
1599  max_value, max(min_value, range_delta + current)
1600  )
1601  if percentage:
1602  data[humidifier.ATTR_HUMIDITY] = percentage
1603 
1604  # Input Number Value
1605  elif instance == f"{input_number.DOMAIN}.{input_number.ATTR_VALUE}":
1606  range_delta = float(range_delta)
1607  service = input_number.SERVICE_SET_VALUE
1608  min_value = float(entity.attributes[input_number.ATTR_MIN])
1609  max_value = float(entity.attributes[input_number.ATTR_MAX])
1610  current = float(entity.state)
1611  data[input_number.ATTR_VALUE] = response_value = min(
1612  max_value, max(min_value, range_delta + current)
1613  )
1614 
1615  # Number Value
1616  elif instance == f"{number.DOMAIN}.{number.ATTR_VALUE}":
1617  range_delta = float(range_delta)
1618  service = number.SERVICE_SET_VALUE
1619  min_value = float(entity.attributes[number.ATTR_MIN])
1620  max_value = float(entity.attributes[number.ATTR_MAX])
1621  current = float(entity.state)
1622  data[number.ATTR_VALUE] = response_value = min(
1623  max_value, max(min_value, range_delta + current)
1624  )
1625 
1626  # Vacuum Fan Speed
1627  elif instance == f"{vacuum.DOMAIN}.{vacuum.ATTR_FAN_SPEED}":
1628  range_delta = int(range_delta)
1629  service = vacuum.SERVICE_SET_FAN_SPEED
1630  speed_list = entity.attributes[vacuum.ATTR_FAN_SPEED_LIST]
1631  current_speed = entity.attributes[vacuum.ATTR_FAN_SPEED]
1632  current_speed_index = next(
1633  (i for i, v in enumerate(speed_list) if v == current_speed), 0
1634  )
1635  new_speed_index = min(
1636  len(speed_list) - 1, max(0, current_speed_index + range_delta)
1637  )
1638  speed = next(
1639  (v for i, v in enumerate(speed_list) if i == new_speed_index), None
1640  )
1641  data[vacuum.ATTR_FAN_SPEED] = response_value = speed
1642 
1643  # Valve Position
1644  elif instance == f"{valve.DOMAIN}.{valve.ATTR_POSITION}":
1645  range_delta = int(range_delta * 20) if range_delta_default else int(range_delta)
1646  service = valve.SERVICE_SET_VALVE_POSITION
1647  if not (current := entity.attributes.get(valve.ATTR_POSITION)):
1648  msg = f"Unable to determine {entity.entity_id} current position"
1649  raise AlexaInvalidValueError(msg)
1650  position = response_value = min(100, max(0, range_delta + current))
1651  if position == 100:
1652  service = valve.SERVICE_OPEN_VALVE
1653  elif position == 0:
1654  service = valve.SERVICE_CLOSE_VALVE
1655  else:
1656  data[valve.ATTR_POSITION] = position
1657 
1658  else:
1659  raise AlexaInvalidDirectiveError(DIRECTIVE_NOT_SUPPORTED)
1660 
1661  await hass.services.async_call(
1662  domain, service, data, blocking=False, context=context
1663  )
1664 
1665  response = directive.response()
1666  response.add_context_property(
1667  {
1668  "namespace": "Alexa.RangeController",
1669  "instance": instance,
1670  "name": "rangeValue",
1671  "value": response_value,
1672  }
1673  )
1674 
1675  return response
1676 
1677 
1678 @HANDLERS.register(("Alexa.ChannelController", "ChangeChannel"))
1680  hass: ha.HomeAssistant,
1681  config: AbstractConfig,
1682  directive: AlexaDirective,
1683  context: ha.Context,
1684 ) -> AlexaResponse:
1685  """Process a change channel request."""
1686  channel = "0"
1687  entity = directive.entity
1688  channel_payload = directive.payload["channel"]
1689  metadata_payload = directive.payload["channelMetadata"]
1690  payload_name = "number"
1691 
1692  if "number" in channel_payload:
1693  channel = channel_payload["number"]
1694  payload_name = "number"
1695  elif "callSign" in channel_payload:
1696  channel = channel_payload["callSign"]
1697  payload_name = "callSign"
1698  elif "affiliateCallSign" in channel_payload:
1699  channel = channel_payload["affiliateCallSign"]
1700  payload_name = "affiliateCallSign"
1701  elif "uri" in channel_payload:
1702  channel = channel_payload["uri"]
1703  payload_name = "uri"
1704  elif "name" in metadata_payload:
1705  channel = metadata_payload["name"]
1706  payload_name = "callSign"
1707 
1708  data: dict[str, Any] = {
1709  ATTR_ENTITY_ID: entity.entity_id,
1710  media_player.const.ATTR_MEDIA_CONTENT_ID: channel,
1711  media_player.const.ATTR_MEDIA_CONTENT_TYPE: (
1712  media_player.const.MEDIA_TYPE_CHANNEL
1713  ),
1714  }
1715 
1716  await hass.services.async_call(
1717  entity.domain,
1718  media_player.const.SERVICE_PLAY_MEDIA,
1719  data,
1720  blocking=False,
1721  context=context,
1722  )
1723 
1724  response = directive.response()
1725 
1726  response.add_context_property(
1727  {
1728  "namespace": "Alexa.ChannelController",
1729  "name": "channel",
1730  "value": {payload_name: channel},
1731  }
1732  )
1733 
1734  return response
1735 
1736 
1737 @HANDLERS.register(("Alexa.ChannelController", "SkipChannels"))
1739  hass: ha.HomeAssistant,
1740  config: AbstractConfig,
1741  directive: AlexaDirective,
1742  context: ha.Context,
1743 ) -> AlexaResponse:
1744  """Process a skipchannel request."""
1745  channel = int(directive.payload["channelCount"])
1746  entity = directive.entity
1747 
1748  data: dict[str, Any] = {ATTR_ENTITY_ID: entity.entity_id}
1749 
1750  if channel < 0:
1751  service_media = SERVICE_MEDIA_PREVIOUS_TRACK
1752  else:
1753  service_media = SERVICE_MEDIA_NEXT_TRACK
1754 
1755  for _ in range(abs(channel)):
1756  await hass.services.async_call(
1757  entity.domain, service_media, data, blocking=False, context=context
1758  )
1759 
1760  response = directive.response()
1761 
1762  response.add_context_property(
1763  {
1764  "namespace": "Alexa.ChannelController",
1765  "name": "channel",
1766  "value": {"number": ""},
1767  }
1768  )
1769 
1770  return response
1771 
1772 
1773 @HANDLERS.register(("Alexa.SeekController", "AdjustSeekPosition"))
1774 async def async_api_seek(
1775  hass: ha.HomeAssistant,
1776  config: AbstractConfig,
1777  directive: AlexaDirective,
1778  context: ha.Context,
1779 ) -> AlexaResponse:
1780  """Process a seek request."""
1781  entity = directive.entity
1782  position_delta = int(directive.payload["deltaPositionMilliseconds"])
1783 
1784  current_position = entity.attributes.get(media_player.ATTR_MEDIA_POSITION)
1785  if not current_position:
1786  msg = f"{entity} did not return the current media position."
1788 
1789  seek_position = max(int(current_position) + int(position_delta / 1000), 0)
1790 
1791  media_duration = entity.attributes.get(media_player.ATTR_MEDIA_DURATION)
1792  if media_duration and 0 < int(media_duration) < seek_position:
1793  seek_position = media_duration
1794 
1795  data: dict[str, Any] = {
1796  ATTR_ENTITY_ID: entity.entity_id,
1797  media_player.ATTR_MEDIA_SEEK_POSITION: seek_position,
1798  }
1799 
1800  await hass.services.async_call(
1801  media_player.DOMAIN,
1802  media_player.SERVICE_MEDIA_SEEK,
1803  data,
1804  blocking=False,
1805  context=context,
1806  )
1807 
1808  # convert seconds to milliseconds for StateReport.
1809  seek_position = int(seek_position * 1000)
1810 
1811  payload: dict[str, Any] = {
1812  "properties": [{"name": "positionMilliseconds", "value": seek_position}]
1813  }
1814  return directive.response(
1815  name="StateReport", namespace="Alexa.SeekController", payload=payload
1816  )
1817 
1818 
1819 @HANDLERS.register(("Alexa.EqualizerController", "SetMode"))
1821  hass: ha.HomeAssistant,
1822  config: AbstractConfig,
1823  directive: AlexaDirective,
1824  context: ha.Context,
1825 ) -> AlexaResponse:
1826  """Process a SetMode request for EqualizerController."""
1827  mode = directive.payload["mode"]
1828  entity = directive.entity
1829  data: dict[str, Any] = {ATTR_ENTITY_ID: entity.entity_id}
1830 
1831  sound_mode_list = entity.attributes.get(media_player.const.ATTR_SOUND_MODE_LIST)
1832  if sound_mode_list and mode.lower() in sound_mode_list:
1833  data[media_player.const.ATTR_SOUND_MODE] = mode.lower()
1834  else:
1835  msg = f"failed to map sound mode {mode} to a mode on {entity.entity_id}"
1836  raise AlexaInvalidValueError(msg)
1837 
1838  await hass.services.async_call(
1839  entity.domain,
1840  media_player.SERVICE_SELECT_SOUND_MODE,
1841  data,
1842  blocking=False,
1843  context=context,
1844  )
1845 
1846  return directive.response()
1847 
1848 
1849 @HANDLERS.register(("Alexa.EqualizerController", "AdjustBands"))
1850 @HANDLERS.register(("Alexa.EqualizerController", "ResetBands"))
1851 @HANDLERS.register(("Alexa.EqualizerController", "SetBands"))
1853  hass: ha.HomeAssistant,
1854  config: AbstractConfig,
1855  directive: AlexaDirective,
1856  context: ha.Context,
1857 ) -> AlexaResponse:
1858  """Handle an AdjustBands, ResetBands, SetBands request.
1859 
1860  Only mode directives are currently supported for the EqualizerController.
1861  """
1862  # Currently bands directives are not supported.
1863  raise AlexaInvalidDirectiveError(DIRECTIVE_NOT_SUPPORTED)
1864 
1865 
1866 @HANDLERS.register(("Alexa.TimeHoldController", "Hold"))
1867 async def async_api_hold(
1868  hass: ha.HomeAssistant,
1869  config: AbstractConfig,
1870  directive: AlexaDirective,
1871  context: ha.Context,
1872 ) -> AlexaResponse:
1873  """Process a TimeHoldController Hold request."""
1874  entity = directive.entity
1875  data: dict[str, Any] = {ATTR_ENTITY_ID: entity.entity_id}
1876 
1877  if entity.domain == timer.DOMAIN:
1878  service = timer.SERVICE_PAUSE
1879 
1880  elif entity.domain == vacuum.DOMAIN:
1881  service = vacuum.SERVICE_START_PAUSE
1882 
1883  else:
1884  raise AlexaInvalidDirectiveError(DIRECTIVE_NOT_SUPPORTED)
1885 
1886  await hass.services.async_call(
1887  entity.domain, service, data, blocking=False, context=context
1888  )
1889 
1890  return directive.response()
1891 
1892 
1893 @HANDLERS.register(("Alexa.TimeHoldController", "Resume"))
1895  hass: ha.HomeAssistant,
1896  config: AbstractConfig,
1897  directive: AlexaDirective,
1898  context: ha.Context,
1899 ) -> AlexaResponse:
1900  """Process a TimeHoldController Resume request."""
1901  entity = directive.entity
1902  data: dict[str, Any] = {ATTR_ENTITY_ID: entity.entity_id}
1903 
1904  if entity.domain == timer.DOMAIN:
1905  service = timer.SERVICE_START
1906 
1907  elif entity.domain == vacuum.DOMAIN:
1908  service = vacuum.SERVICE_START_PAUSE
1909 
1910  else:
1911  raise AlexaInvalidDirectiveError(DIRECTIVE_NOT_SUPPORTED)
1912 
1913  await hass.services.async_call(
1914  entity.domain, service, data, blocking=False, context=context
1915  )
1916 
1917  return directive.response()
1918 
1919 
1920 @HANDLERS.register(("Alexa.CameraStreamController", "InitializeCameraStreams"))
1922  hass: ha.HomeAssistant,
1923  config: AbstractConfig,
1924  directive: AlexaDirective,
1925  context: ha.Context,
1926 ) -> AlexaResponse:
1927  """Process a InitializeCameraStreams request."""
1928  entity = directive.entity
1929  stream_source = await camera.async_request_stream(hass, entity.entity_id, fmt="hls")
1930  state = hass.states.get(entity.entity_id)
1931  assert state
1932  camera_image = state.attributes[ATTR_ENTITY_PICTURE]
1933 
1934  try:
1935  external_url = network.get_url(
1936  hass,
1937  allow_internal=False,
1938  allow_ip=False,
1939  require_ssl=True,
1940  require_standard_port=True,
1941  )
1942  except network.NoURLAvailableError as err:
1943  raise AlexaInvalidValueError(
1944  "Failed to find suitable URL to serve to Alexa"
1945  ) from err
1946 
1947  payload: dict[str, Any] = {
1948  "cameraStreams": [
1949  {
1950  "uri": f"{external_url}{stream_source}",
1951  "protocol": "HLS",
1952  "resolution": {"width": 1280, "height": 720},
1953  "authorizationType": "NONE",
1954  "videoCodec": "H264",
1955  "audioCodec": "AAC",
1956  }
1957  ],
1958  "imageUri": f"{external_url}{camera_image}",
1959  }
1960  return directive.response(
1961  name="Response", namespace="Alexa.CameraStreamController", payload=payload
1962  )
list[AlexaEntity] async_get_entities(HomeAssistant hass, AbstractConfig config)
Definition: entities.py:374
AlexaResponse async_api_seek(ha.HomeAssistant hass, AbstractConfig config, AlexaDirective directive, ha.Context context)
Definition: handlers.py:1779
AlexaResponse async_api_select_input(ha.HomeAssistant hass, AbstractConfig config, AlexaDirective directive, ha.Context context)
Definition: handlers.py:584
AlexaResponse async_api_activate(ha.HomeAssistant hass, AbstractConfig config, AlexaDirective directive, ha.Context context)
Definition: handlers.py:424
AlexaResponse async_api_toggle_on(ha.HomeAssistant hass, AbstractConfig config, AlexaDirective directive, ha.Context context)
Definition: handlers.py:1323
AlexaResponse async_api_disarm(ha.HomeAssistant hass, AbstractConfig config, AlexaDirective directive, ha.Context context)
Definition: handlers.py:1150
AlexaResponse async_api_resume(ha.HomeAssistant hass, AbstractConfig config, AlexaDirective directive, ha.Context context)
Definition: handlers.py:1899
AlexaResponse async_api_adjust_target_temp(ha.HomeAssistant hass, AbstractConfig config, AlexaDirective directive, ha.Context context)
Definition: handlers.py:925
AlexaResponse async_api_accept_grant(ha.HomeAssistant hass, AbstractConfig config, AlexaDirective directive, ha.Context context)
Definition: handlers.py:151
AlexaResponse async_api_discovery(ha.HomeAssistant hass, AbstractConfig config, AlexaDirective directive, ha.Context context)
Definition: handlers.py:120
AlexaResponse async_api_toggle_off(ha.HomeAssistant hass, AbstractConfig config, AlexaDirective directive, ha.Context context)
Definition: handlers.py:1369
AlexaResponse async_api_turn_on(ha.HomeAssistant hass, AbstractConfig config, AlexaDirective directive, ha.Context context)
Definition: handlers.py:175
AlexaResponse async_api_reportstate(ha.HomeAssistant hass, AbstractConfig config, AlexaDirective directive, ha.Context context)
Definition: handlers.py:1085
AlexaResponse async_api_set_target_temp(ha.HomeAssistant hass, AbstractConfig config, AlexaDirective directive, ha.Context context)
Definition: handlers.py:856
AlexaResponse async_api_set_color(ha.HomeAssistant hass, AbstractConfig config, AlexaDirective directive, ha.Context context)
Definition: handlers.py:328
AlexaResponse async_api_changechannel(ha.HomeAssistant hass, AbstractConfig config, AlexaDirective directive, ha.Context context)
Definition: handlers.py:1684
AlexaResponse async_api_adjust_volume_step(ha.HomeAssistant hass, AbstractConfig config, AlexaDirective directive, ha.Context context)
Definition: handlers.py:666
AlexaResponse async_api_previous(ha.HomeAssistant hass, AbstractConfig config, AlexaDirective directive, ha.Context context)
Definition: handlers.py:815
AlexaResponse async_api_bands_directive(ha.HomeAssistant hass, AbstractConfig config, AlexaDirective directive, ha.Context context)
Definition: handlers.py:1857
AlexaResponse async_api_adjust_brightness(ha.HomeAssistant hass, AbstractConfig config, AlexaDirective directive, ha.Context context)
Definition: handlers.py:302
AlexaResponse async_api_skipchannel(ha.HomeAssistant hass, AbstractConfig config, AlexaDirective directive, ha.Context context)
Definition: handlers.py:1743
AlexaResponse async_api_initialize_camera_stream(ha.HomeAssistant hass, AbstractConfig config, AlexaDirective directive, ha.Context context)
Definition: handlers.py:1926
AlexaResponse async_api_adjust_range(ha.HomeAssistant hass, AbstractConfig config, AlexaDirective directive, ha.Context context)
Definition: handlers.py:1524
AlexaResponse async_api_set_range(ha.HomeAssistant hass, AbstractConfig config, AlexaDirective directive, ha.Context context)
Definition: handlers.py:1408
AlexaResponse async_api_decrease_color_temp(ha.HomeAssistant hass, AbstractConfig config, AlexaDirective directive, ha.Context context)
Definition: handlers.py:376
float temperature_from_object(ha.HomeAssistant hass, dict[str, Any] temp_obj, bool interval=False)
Definition: handlers.py:833
AlexaResponse async_api_increase_color_temp(ha.HomeAssistant hass, AbstractConfig config, AlexaDirective directive, ha.Context context)
Definition: handlers.py:400
AlexaResponse async_api_set_mode(ha.HomeAssistant hass, AbstractConfig config, AlexaDirective directive, ha.Context context)
Definition: handlers.py:1189
AlexaResponse async_api_set_mute(ha.HomeAssistant hass, AbstractConfig config, AlexaDirective directive, ha.Context context)
Definition: handlers.py:705
AlexaResponse async_api_next(ha.HomeAssistant hass, AbstractConfig config, AlexaDirective directive, ha.Context context)
Definition: handlers.py:797
AlexaResponse async_api_set_brightness(ha.HomeAssistant hass, AbstractConfig config, AlexaDirective directive, ha.Context context)
Definition: handlers.py:280
AlexaResponse async_api_adjust_mode(ha.HomeAssistant hass, AbstractConfig config, AlexaDirective directive, ha.Context context)
Definition: handlers.py:1305
AlexaResponse async_api_turn_off(ha.HomeAssistant hass, AbstractConfig config, AlexaDirective directive, ha.Context context)
Definition: handlers.py:227
AlexaResponse async_api_set_volume(ha.HomeAssistant hass, AbstractConfig config, AlexaDirective directive, ha.Context context)
Definition: handlers.py:561
AlexaResponse async_api_set_eq_mode(ha.HomeAssistant hass, AbstractConfig config, AlexaDirective directive, ha.Context context)
Definition: handlers.py:1825
AlexaResponse async_api_lock(ha.HomeAssistant hass, AbstractConfig config, AlexaDirective directive, ha.Context context)
Definition: handlers.py:488
AlexaResponse async_api_play(ha.HomeAssistant hass, AbstractConfig config, AlexaDirective directive, ha.Context context)
Definition: handlers.py:727
AlexaResponse async_api_deactivate(ha.HomeAssistant hass, AbstractConfig config, AlexaDirective directive, ha.Context context)
Definition: handlers.py:459
AlexaResponse async_api_set_thermostat_mode(ha.HomeAssistant hass, AbstractConfig config, AlexaDirective directive, ha.Context context)
Definition: handlers.py:1011
AlexaResponse async_api_stop(ha.HomeAssistant hass, AbstractConfig config, AlexaDirective directive, ha.Context context)
Definition: handlers.py:763
AlexaResponse async_api_adjust_volume(ha.HomeAssistant hass, AbstractConfig config, AlexaDirective directive, ha.Context context)
Definition: handlers.py:633
AlexaResponse async_api_pause(ha.HomeAssistant hass, AbstractConfig config, AlexaDirective directive, ha.Context context)
Definition: handlers.py:745
AlexaResponse async_api_unlock(ha.HomeAssistant hass, AbstractConfig config, AlexaDirective directive, ha.Context context)
Definition: handlers.py:512
AlexaResponse async_api_set_color_temperature(ha.HomeAssistant hass, AbstractConfig config, AlexaDirective directive, ha.Context context)
Definition: handlers.py:354
AlexaResponse async_api_hold(ha.HomeAssistant hass, AbstractConfig config, AlexaDirective directive, ha.Context context)
Definition: handlers.py:1872
AlexaResponse async_api_arm(ha.HomeAssistant hass, AbstractConfig config, AlexaDirective directive, ha.Context context)
Definition: handlers.py:1096
CALLBACK_TYPE|None async_enable_proactive_mode(HomeAssistant hass, AbstractConfig smart_home_config)