Home Assistant Unofficial Reference 2024.12.1
capabilities.py
Go to the documentation of this file.
1 """Alexa capabilities."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Generator
6 import logging
7 from typing import Any
8 
9 from homeassistant.components import (
10  button,
11  climate,
12  cover,
13  fan,
14  humidifier,
15  image_processing,
16  input_button,
17  input_number,
18  light,
19  media_player,
20  number,
21  remote,
22  timer,
23  vacuum,
24  valve,
25  water_heater,
26 )
28  AlarmControlPanelEntityFeature,
29  AlarmControlPanelState,
30  CodeFormat,
31 )
32 from homeassistant.components.climate import HVACMode
33 from homeassistant.components.lock import LockState
34 from homeassistant.const import (
35  ATTR_CODE_FORMAT,
36  ATTR_SUPPORTED_FEATURES,
37  ATTR_TEMPERATURE,
38  ATTR_UNIT_OF_MEASUREMENT,
39  PERCENTAGE,
40  STATE_IDLE,
41  STATE_OFF,
42  STATE_ON,
43  STATE_PAUSED,
44  STATE_PLAYING,
45  STATE_UNAVAILABLE,
46  STATE_UNKNOWN,
47  UnitOfLength,
48  UnitOfMass,
49  UnitOfTemperature,
50  UnitOfVolume,
51 )
52 from homeassistant.core import HomeAssistant, State
53 import homeassistant.util.color as color_util
54 import homeassistant.util.dt as dt_util
55 
56 from .const import (
57  API_TEMP_UNITS,
58  API_THERMOSTAT_MODES,
59  API_THERMOSTAT_PRESETS,
60  DATE_FORMAT,
61  PRESET_MODE_NA,
62  Inputs,
63 )
64 from .errors import UnsupportedProperty
65 from .resources import (
66  AlexaCapabilityResource,
67  AlexaGlobalCatalog,
68  AlexaModeResource,
69  AlexaPresetResource,
70  AlexaSemantics,
71 )
72 
73 _LOGGER = logging.getLogger(__name__)
74 
75 UNIT_TO_CATALOG_TAG = {
76  UnitOfTemperature.CELSIUS: AlexaGlobalCatalog.UNIT_TEMPERATURE_CELSIUS,
77  UnitOfTemperature.FAHRENHEIT: AlexaGlobalCatalog.UNIT_TEMPERATURE_FAHRENHEIT,
78  UnitOfTemperature.KELVIN: AlexaGlobalCatalog.UNIT_TEMPERATURE_KELVIN,
79  UnitOfLength.METERS: AlexaGlobalCatalog.UNIT_DISTANCE_METERS,
80  UnitOfLength.KILOMETERS: AlexaGlobalCatalog.UNIT_DISTANCE_KILOMETERS,
81  UnitOfLength.INCHES: AlexaGlobalCatalog.UNIT_DISTANCE_INCHES,
82  UnitOfLength.FEET: AlexaGlobalCatalog.UNIT_DISTANCE_FEET,
83  UnitOfLength.YARDS: AlexaGlobalCatalog.UNIT_DISTANCE_YARDS,
84  UnitOfLength.MILES: AlexaGlobalCatalog.UNIT_DISTANCE_MILES,
85  UnitOfMass.GRAMS: AlexaGlobalCatalog.UNIT_MASS_GRAMS,
86  UnitOfMass.KILOGRAMS: AlexaGlobalCatalog.UNIT_MASS_KILOGRAMS,
87  UnitOfMass.POUNDS: AlexaGlobalCatalog.UNIT_WEIGHT_POUNDS,
88  UnitOfMass.OUNCES: AlexaGlobalCatalog.UNIT_WEIGHT_OUNCES,
89  UnitOfVolume.LITERS: AlexaGlobalCatalog.UNIT_VOLUME_LITERS,
90  UnitOfVolume.CUBIC_FEET: AlexaGlobalCatalog.UNIT_VOLUME_CUBIC_FEET,
91  UnitOfVolume.CUBIC_METERS: AlexaGlobalCatalog.UNIT_VOLUME_CUBIC_METERS,
92  UnitOfVolume.GALLONS: AlexaGlobalCatalog.UNIT_VOLUME_GALLONS,
93  PERCENTAGE: AlexaGlobalCatalog.UNIT_PERCENT,
94  "preset": AlexaGlobalCatalog.SETTING_PRESET,
95 }
96 
97 
98 def get_resource_by_unit_of_measurement(entity: State) -> str:
99  """Translate the unit of measurement to an Alexa Global Catalog keyword."""
100  unit: str = entity.attributes.get("unit_of_measurement", "preset")
101  return UNIT_TO_CATALOG_TAG.get(unit, AlexaGlobalCatalog.SETTING_PRESET)
102 
103 
105  """Base class for Alexa capability interfaces.
106 
107  The Smart Home Skills API defines a number of "capability interfaces",
108  roughly analogous to domains in Home Assistant. The supported interfaces
109  describe what actions can be performed on a particular device.
110 
111  https://developer.amazon.com/docs/device-apis/message-guide.html
112  """
113 
114  _resource: AlexaCapabilityResource | None
115  _semantics: AlexaSemantics | None
116  supported_locales: set[str] = {"en-US"}
117 
118  def __init__(
119  self,
120  entity: State,
121  instance: str | None = None,
122  non_controllable_properties: bool | None = None,
123  ) -> None:
124  """Initialize an Alexa capability."""
125  self.entityentity = entity
126  self.instanceinstance = instance
127  self._non_controllable_properties_non_controllable_properties = non_controllable_properties
128 
129  def name(self) -> str:
130  """Return the Alexa API name of this interface."""
131  raise NotImplementedError
132 
133  def properties_supported(self) -> list[dict]:
134  """Return what properties this entity supports."""
135  return []
136 
138  """Return True if properties asynchronously reported."""
139  return False
140 
141  def properties_retrievable(self) -> bool:
142  """Return True if properties can be retrieved."""
143  return False
144 
145  def properties_non_controllable(self) -> bool | None:
146  """Return True if non controllable."""
147  return self._non_controllable_properties_non_controllable_properties
148 
149  def get_property(self, name: str) -> dict[str, Any]:
150  """Read and return a property.
151 
152  Return value should be a dict, or raise UnsupportedProperty.
153 
154  Properties can also have a timeOfSample and uncertaintyInMilliseconds,
155  but returning those metadata is not yet implemented.
156  """
157  raise UnsupportedProperty(name)
158 
159  def supports_deactivation(self) -> bool | None:
160  """Applicable only to scenes."""
161 
162  def capability_proactively_reported(self) -> bool | None:
163  """Return True if the capability is proactively reported.
164 
165  Set properties_proactively_reported() for proactively reported properties.
166  Applicable to DoorbellEventSource.
167  """
168 
169  def capability_resources(self) -> dict[str, list[dict[str, Any]]]:
170  """Return the capability object.
171 
172  Applicable to ToggleController, RangeController, and ModeController interfaces.
173  """
174  return {}
175 
176  def configuration(self) -> dict[str, Any] | None:
177  """Return the configuration object.
178 
179  Applicable to the ThermostatController, SecurityControlPanel, ModeController,
180  RangeController, and EventDetectionSensor.
181  """
182 
183  def configurations(self) -> dict[str, Any] | None:
184  """Return the configurations object.
185 
186  The plural configurations object is different that the singular configuration
187  object. Applicable to EqualizerController interface.
188  """
189 
190  def inputs(self) -> list[dict[str, str]] | None:
191  """Applicable only to media players."""
192 
193  def semantics(self) -> dict[str, Any] | None:
194  """Return the semantics object.
195 
196  Applicable to ToggleController, RangeController, and ModeController interfaces.
197  """
198 
199  def supported_operations(self) -> list[str]:
200  """Return the supportedOperations object."""
201  return []
202 
203  def camera_stream_configurations(self) -> list[dict[str, Any]] | None:
204  """Applicable only to CameraStreamController."""
205 
206  def serialize_discovery(self) -> dict[str, Any]:
207  """Serialize according to the Discovery API."""
208  result: dict[str, Any] = {
209  "type": "AlexaInterface",
210  "interface": self.name(),
211  "version": "3",
212  }
213 
214  if (instance := self.instance) is not None:
215  result["instance"] = instance
216 
217  if properties_supported := self.properties_supported():
218  result["properties"] = {
219  "supported": properties_supported,
220  "proactivelyReported": self.properties_proactively_reported(),
221  "retrievable": self.properties_retrievable(),
222  }
223 
224  if (proactively_reported := self.capability_proactively_reported()) is not None:
225  result["proactivelyReported"] = proactively_reported
226 
227  if (non_controllable := self.properties_non_controllable()) is not None:
228  result["properties"]["nonControllable"] = non_controllable
229 
230  if (supports_deactivation := self.supports_deactivation()) is not None:
231  result["supportsDeactivation"] = supports_deactivation
232 
233  if capability_resources := self.capability_resources():
234  result["capabilityResources"] = capability_resources
235 
236  if configuration := self.configuration():
237  result["configuration"] = configuration
238 
239  # The plural configurations object is different than the singular
240  # configuration object above.
241  if configurations := self.configurations():
242  result["configurations"] = configurations
243 
244  if semantics := self.semantics():
245  result["semantics"] = semantics
246 
247  if supported_operations := self.supported_operations():
248  result["supportedOperations"] = supported_operations
249 
250  if inputs := self.inputs():
251  result["inputs"] = inputs
252 
253  if camera_stream_configurations := self.camera_stream_configurations():
254  result["cameraStreamConfigurations"] = camera_stream_configurations
255 
256  return result
257 
258  def serialize_properties(self) -> Generator[dict[str, Any]]:
259  """Return properties serialized for an API response."""
260  for prop in self.properties_supportedproperties_supported():
261  prop_name = prop["name"]
262  try:
263  prop_value = self.get_propertyget_property(prop_name)
264  except UnsupportedProperty:
265  raise
266  except Exception:
267  _LOGGER.exception(
268  "Unexpected error getting %s.%s property from %s",
269  self.namename(),
270  prop_name,
271  self.entityentity,
272  )
273  prop_value = None
274 
275  if prop_value is None:
276  continue
277 
278  result = {
279  "name": prop_name,
280  "namespace": self.namename(),
281  "value": prop_value,
282  "timeOfSample": dt_util.utcnow().strftime(DATE_FORMAT),
283  "uncertaintyInMilliseconds": 0,
284  }
285  if (instance := self.instanceinstance) is not None:
286  result["instance"] = instance
287 
288  yield result
289 
290 
292  """Implements Alexa Interface.
293 
294  Although endpoints implement this interface implicitly,
295  The API suggests you should explicitly include this interface.
296 
297  https://developer.amazon.com/docs/device-apis/alexa-interface.html
298 
299  To compare current supported locales in Home Assistant
300  with Alexa supported locales, run the following script:
301  python -m script.alexa_locales
302  """
303 
304  supported_locales = {
305  "ar-SA",
306  "de-DE",
307  "en-AU",
308  "en-CA",
309  "en-GB",
310  "en-IN",
311  "en-US",
312  "es-ES",
313  "es-MX",
314  "es-US",
315  "fr-CA",
316  "fr-FR",
317  "hi-IN",
318  "it-IT",
319  "ja-JP",
320  "pt-BR",
321  }
322 
323  def name(self) -> str:
324  """Return the Alexa API name of this interface."""
325  return "Alexa"
326 
327 
329  """Implements Alexa.EndpointHealth.
330 
331  https://developer.amazon.com/docs/smarthome/state-reporting-for-a-smart-home-skill.html#report-state-when-alexa-requests-it
332  """
333 
334  supported_locales = {
335  "ar-SA",
336  "de-DE",
337  "en-AU",
338  "en-CA",
339  "en-GB",
340  "en-IN",
341  "en-US",
342  "es-ES",
343  "es-MX",
344  "es-US",
345  "fr-CA",
346  "fr-FR",
347  "hi-IN",
348  "it-IT",
349  "ja-JP",
350  "pt-BR",
351  }
352 
353  def __init__(self, hass: HomeAssistant, entity: State) -> None:
354  """Initialize the entity."""
355  super().__init__(entity)
356  self.hasshass = hass
357 
358  def name(self) -> str:
359  """Return the Alexa API name of this interface."""
360  return "Alexa.EndpointHealth"
361 
362  def properties_supported(self) -> list[dict[str, str]]:
363  """Return what properties this entity supports."""
364  return [{"name": "connectivity"}]
365 
367  """Return True if properties asynchronously reported."""
368  return True
369 
370  def properties_retrievable(self) -> bool:
371  """Return True if properties can be retrieved."""
372  return True
373 
374  def get_property(self, name: str) -> Any:
375  """Read and return a property."""
376  if name != "connectivity":
377  raise UnsupportedProperty(name)
378 
379  if self.entityentity.state == STATE_UNAVAILABLE:
380  return {"value": "UNREACHABLE"}
381  return {"value": "OK"}
382 
383 
385  """Implements Alexa.PowerController.
386 
387  https://developer.amazon.com/docs/device-apis/alexa-powercontroller.html
388  """
389 
390  supported_locales = {
391  "ar-SA",
392  "de-DE",
393  "en-AU",
394  "en-CA",
395  "en-GB",
396  "en-IN",
397  "en-US",
398  "es-ES",
399  "es-MX",
400  "es-US",
401  "fr-CA",
402  "fr-FR",
403  "hi-IN",
404  "it-IT",
405  "ja-JP",
406  "pt-BR",
407  }
408 
409  def name(self) -> str:
410  """Return the Alexa API name of this interface."""
411  return "Alexa.PowerController"
412 
413  def properties_supported(self) -> list[dict[str, str]]:
414  """Return what properties this entity supports."""
415  return [{"name": "powerState"}]
416 
418  """Return True if properties asynchronously reported."""
419  return True
420 
421  def properties_retrievable(self) -> bool:
422  """Return True if properties can be retrieved."""
423  return True
424 
425  def get_property(self, name: str) -> Any:
426  """Read and return a property."""
427  if name != "powerState":
428  raise UnsupportedProperty(name)
429 
430  if self.entityentity.domain == climate.DOMAIN:
431  is_on = self.entityentity.state != climate.HVACMode.OFF
432  elif self.entityentity.domain == fan.DOMAIN:
433  is_on = self.entityentity.state == fan.STATE_ON
434  elif self.entityentity.domain == humidifier.DOMAIN:
435  is_on = self.entityentity.state == humidifier.STATE_ON
436  elif self.entityentity.domain == remote.DOMAIN:
437  is_on = self.entityentity.state not in (STATE_OFF, STATE_UNKNOWN)
438  elif self.entityentity.domain == vacuum.DOMAIN:
439  is_on = self.entityentity.state == vacuum.STATE_CLEANING
440  elif self.entityentity.domain == timer.DOMAIN:
441  is_on = self.entityentity.state != STATE_IDLE
442  elif self.entityentity.domain == water_heater.DOMAIN:
443  is_on = self.entityentity.state not in (STATE_OFF, STATE_UNKNOWN)
444  else:
445  is_on = self.entityentity.state != STATE_OFF
446 
447  return "ON" if is_on else "OFF"
448 
449 
451  """Implements Alexa.LockController.
452 
453  https://developer.amazon.com/docs/device-apis/alexa-lockcontroller.html
454  """
455 
456  supported_locales = {
457  "ar-SA",
458  "de-DE",
459  "en-AU",
460  "en-CA",
461  "en-GB",
462  "en-IN",
463  "en-US",
464  "es-ES",
465  "es-MX",
466  "es-US",
467  "fr-CA",
468  "fr-FR",
469  "hi-IN",
470  "it-IT",
471  "ja-JP",
472  "pt-BR",
473  }
474 
475  def name(self) -> str:
476  """Return the Alexa API name of this interface."""
477  return "Alexa.LockController"
478 
479  def properties_supported(self) -> list[dict[str, str]]:
480  """Return what properties this entity supports."""
481  return [{"name": "lockState"}]
482 
483  def properties_retrievable(self) -> bool:
484  """Return True if properties can be retrieved."""
485  return True
486 
488  """Return True if properties asynchronously reported."""
489  return True
490 
491  def get_property(self, name: str) -> Any:
492  """Read and return a property."""
493  if name != "lockState":
494  raise UnsupportedProperty(name)
495 
496  # If its unlocking its still locked and not unlocked yet
497  if self.entityentity.state in (LockState.UNLOCKING, LockState.LOCKED):
498  return "LOCKED"
499  # If its locking its still unlocked and not locked yet
500  if self.entityentity.state in (LockState.LOCKING, LockState.UNLOCKED):
501  return "UNLOCKED"
502  return "JAMMED"
503 
504 
506  """Implements Alexa.SceneController.
507 
508  https://developer.amazon.com/docs/device-apis/alexa-scenecontroller.html
509  """
510 
511  supported_locales = {
512  "de-DE",
513  "en-AU",
514  "en-CA",
515  "en-GB",
516  "en-IN",
517  "en-US",
518  "es-ES",
519  "es-MX",
520  "es-US",
521  "fr-CA",
522  "fr-FR",
523  "hi-IN",
524  "it-IT",
525  "ja-JP",
526  "pt-BR",
527  }
528 
529  def __init__(self, entity: State, supports_deactivation: bool) -> None:
530  """Initialize the entity."""
531  self._supports_deactivation_supports_deactivation = supports_deactivation
532  super().__init__(entity)
533 
534  def supports_deactivation(self) -> bool | None:
535  """Return True if the Scene controller supports deactivation."""
536  return self._supports_deactivation_supports_deactivation
537 
538  def name(self) -> str:
539  """Return the Alexa API name of this interface."""
540  return "Alexa.SceneController"
541 
542 
544  """Implements Alexa.BrightnessController.
545 
546  https://developer.amazon.com/docs/device-apis/alexa-brightnesscontroller.html
547  """
548 
549  supported_locales = {
550  "ar-SA",
551  "de-DE",
552  "en-AU",
553  "en-CA",
554  "en-GB",
555  "en-IN",
556  "en-US",
557  "es-ES",
558  "es-MX",
559  "es-US",
560  "fr-CA",
561  "fr-FR",
562  "hi-IN",
563  "it-IT",
564  "ja-JP",
565  "pt-BR",
566  }
567 
568  def name(self) -> str:
569  """Return the Alexa API name of this interface."""
570  return "Alexa.BrightnessController"
571 
572  def properties_supported(self) -> list[dict[str, str]]:
573  """Return what properties this entity supports."""
574  return [{"name": "brightness"}]
575 
577  """Return True if properties asynchronously reported."""
578  return True
579 
580  def properties_retrievable(self) -> bool:
581  """Return True if properties can be retrieved."""
582  return True
583 
584  def get_property(self, name: str) -> Any:
585  """Read and return a property."""
586  if name != "brightness":
587  raise UnsupportedProperty(name)
588  if brightness := self.entityentity.attributes.get("brightness"):
589  return round(brightness / 255.0 * 100)
590  return 0
591 
592 
594  """Implements Alexa.ColorController.
595 
596  https://developer.amazon.com/docs/device-apis/alexa-colorcontroller.html
597  """
598 
599  supported_locales = {
600  "de-DE",
601  "en-AU",
602  "en-CA",
603  "en-GB",
604  "en-IN",
605  "en-US",
606  "es-ES",
607  "es-MX",
608  "es-US",
609  "fr-CA",
610  "fr-FR",
611  "hi-IN",
612  "it-IT",
613  "ja-JP",
614  "pt-BR",
615  }
616 
617  def name(self) -> str:
618  """Return the Alexa API name of this interface."""
619  return "Alexa.ColorController"
620 
621  def properties_supported(self) -> list[dict[str, str]]:
622  """Return what properties this entity supports."""
623  return [{"name": "color"}]
624 
626  """Return True if properties asynchronously reported."""
627  return True
628 
629  def properties_retrievable(self) -> bool:
630  """Return True if properties can be retrieved."""
631  return True
632 
633  def get_property(self, name: str) -> Any:
634  """Read and return a property."""
635  if name != "color":
636  raise UnsupportedProperty(name)
637 
638  hue_saturation: tuple[float, float] | None
639  if (hue_saturation := self.entityentity.attributes.get(light.ATTR_HS_COLOR)) is None:
640  hue_saturation = (0, 0)
641  if (brightness := self.entityentity.attributes.get(light.ATTR_BRIGHTNESS)) is None:
642  brightness = 0
643 
644  return {
645  "hue": hue_saturation[0],
646  "saturation": hue_saturation[1] / 100.0,
647  "brightness": brightness / 255.0,
648  }
649 
650 
652  """Implements Alexa.ColorTemperatureController.
653 
654  https://developer.amazon.com/docs/device-apis/alexa-colortemperaturecontroller.html
655  """
656 
657  supported_locales = {
658  "de-DE",
659  "en-AU",
660  "en-CA",
661  "en-GB",
662  "en-IN",
663  "en-US",
664  "es-ES",
665  "es-MX",
666  "es-US",
667  "fr-CA",
668  "fr-FR",
669  "hi-IN",
670  "it-IT",
671  "ja-JP",
672  "pt-BR",
673  }
674 
675  def name(self) -> str:
676  """Return the Alexa API name of this interface."""
677  return "Alexa.ColorTemperatureController"
678 
679  def properties_supported(self) -> list[dict[str, str]]:
680  """Return what properties this entity supports."""
681  return [{"name": "colorTemperatureInKelvin"}]
682 
684  """Return True if properties asynchronously reported."""
685  return True
686 
687  def properties_retrievable(self) -> bool:
688  """Return True if properties can be retrieved."""
689  return True
690 
691  def get_property(self, name: str) -> Any:
692  """Read and return a property."""
693  if name != "colorTemperatureInKelvin":
694  raise UnsupportedProperty(name)
695  if color_temp := self.entityentity.attributes.get("color_temp"):
696  return color_util.color_temperature_mired_to_kelvin(color_temp)
697  return None
698 
699 
701  """Implements Alexa.Speaker.
702 
703  https://developer.amazon.com/docs/device-apis/alexa-speaker.html
704  """
705 
706  supported_locales = {
707  "de-DE",
708  "en-AU",
709  "en-CA",
710  "en-GB",
711  "en-IN",
712  "en-US",
713  "es-ES",
714  "es-MX",
715  "fr-FR", # Not documented as of 2021-12-04, see PR #60489
716  "it-IT",
717  "ja-JP",
718  }
719 
720  def name(self) -> str:
721  """Return the Alexa API name of this interface."""
722  return "Alexa.Speaker"
723 
724  def properties_supported(self) -> list[dict[str, str]]:
725  """Return what properties this entity supports."""
726  properties = [{"name": "volume"}]
727 
728  supported = self.entityentity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
729  if supported & media_player.MediaPlayerEntityFeature.VOLUME_MUTE:
730  properties.append({"name": "muted"})
731 
732  return properties
733 
735  """Return True if properties asynchronously reported."""
736  return True
737 
738  def properties_retrievable(self) -> bool:
739  """Return True if properties can be retrieved."""
740  return True
741 
742  def get_property(self, name: str) -> Any:
743  """Read and return a property."""
744  if name == "volume":
745  current_level = self.entityentity.attributes.get(
746  media_player.ATTR_MEDIA_VOLUME_LEVEL
747  )
748  if current_level is not None:
749  return round(float(current_level) * 100)
750 
751  if name == "muted":
752  return bool(
753  self.entityentity.attributes.get(media_player.ATTR_MEDIA_VOLUME_MUTED)
754  )
755 
756  return None
757 
758 
760  """Implements Alexa.StepSpeaker.
761 
762  https://developer.amazon.com/docs/device-apis/alexa-stepspeaker.html
763  """
764 
765  supported_locales = {
766  "de-DE",
767  "en-AU",
768  "en-CA",
769  "en-GB",
770  "en-IN",
771  "en-US",
772  "es-ES",
773  "fr-FR", # Not documented as of 2021-12-04, see PR #60489
774  "it-IT",
775  }
776 
777  def name(self) -> str:
778  """Return the Alexa API name of this interface."""
779  return "Alexa.StepSpeaker"
780 
781 
783  """Implements Alexa.PlaybackController.
784 
785  https://developer.amazon.com/docs/device-apis/alexa-playbackcontroller.html
786  """
787 
788  supported_locales = {
789  "ar-SA",
790  "de-DE",
791  "en-AU",
792  "en-CA",
793  "en-GB",
794  "en-IN",
795  "en-US",
796  "es-ES",
797  "es-MX",
798  "es-US",
799  "fr-CA",
800  "fr-FR",
801  "hi-IN",
802  "it-IT",
803  "ja-JP",
804  "pt-BR",
805  }
806 
807  def name(self) -> str:
808  """Return the Alexa API name of this interface."""
809  return "Alexa.PlaybackController"
810 
811  def supported_operations(self) -> list[str]:
812  """Return the supportedOperations object.
813 
814  Supported Operations: FastForward, Next, Pause, Play, Previous, Rewind,
815  StartOver, Stop
816  """
817  supported_features = self.entityentity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
818 
819  operations: dict[
820  cover.CoverEntityFeature | media_player.MediaPlayerEntityFeature, str
821  ]
822  if self.entityentity.domain == cover.DOMAIN:
823  operations = {cover.CoverEntityFeature.STOP: "Stop"}
824  else:
825  operations = {
826  media_player.MediaPlayerEntityFeature.NEXT_TRACK: "Next",
827  media_player.MediaPlayerEntityFeature.PAUSE: "Pause",
828  media_player.MediaPlayerEntityFeature.PLAY: "Play",
829  media_player.MediaPlayerEntityFeature.PREVIOUS_TRACK: "Previous",
830  media_player.MediaPlayerEntityFeature.STOP: "Stop",
831  }
832 
833  return [
834  value
835  for operation, value in operations.items()
836  if operation & supported_features
837  ]
838 
839 
841  """Implements Alexa.InputController.
842 
843  https://developer.amazon.com/docs/device-apis/alexa-inputcontroller.html
844  """
845 
846  supported_locales = {
847  "ar-SA",
848  "de-DE",
849  "en-AU",
850  "en-CA",
851  "en-GB",
852  "en-IN",
853  "en-US",
854  "es-ES",
855  "es-MX",
856  "es-US",
857  "fr-CA",
858  "fr-FR",
859  "hi-IN",
860  "it-IT",
861  "ja-JP",
862  "pt-BR",
863  }
864 
865  def name(self) -> str:
866  """Return the Alexa API name of this interface."""
867  return "Alexa.InputController"
868 
869  def inputs(self) -> list[dict[str, str]] | None:
870  """Return the list of valid supported inputs."""
871  source_list: list[Any] = (
872  self.entityentity.attributes.get(media_player.ATTR_INPUT_SOURCE_LIST) or []
873  )
874  return AlexaInputController.get_valid_inputs(source_list)
875 
876  @staticmethod
877  def get_valid_inputs(source_list: list[Any]) -> list[dict[str, str]]:
878  """Return list of supported inputs."""
879  input_list: list[dict[str, str]] = []
880  for source in source_list:
881  if not isinstance(source, str):
882  continue
883  formatted_source = (
884  source.lower().replace("-", "").replace("_", "").replace(" ", "")
885  )
886  if formatted_source in Inputs.VALID_SOURCE_NAME_MAP:
887  input_list.append(
888  {"name": Inputs.VALID_SOURCE_NAME_MAP[formatted_source]}
889  )
890 
891  return input_list
892 
893 
895  """Implements Alexa.TemperatureSensor.
896 
897  https://developer.amazon.com/docs/device-apis/alexa-temperaturesensor.html
898  """
899 
900  supported_locales = {
901  "ar-SA",
902  "de-DE",
903  "en-AU",
904  "en-CA",
905  "en-GB",
906  "en-IN",
907  "en-US",
908  "es-ES",
909  "es-MX",
910  "es-US",
911  "fr-CA",
912  "fr-FR",
913  "hi-IN",
914  "it-IT",
915  "ja-JP",
916  "pt-BR",
917  }
918 
919  def __init__(self, hass: HomeAssistant, entity: State) -> None:
920  """Initialize the entity."""
921  super().__init__(entity)
922  self.hasshass = hass
923 
924  def name(self) -> str:
925  """Return the Alexa API name of this interface."""
926  return "Alexa.TemperatureSensor"
927 
928  def properties_supported(self) -> list[dict[str, str]]:
929  """Return what properties this entity supports."""
930  return [{"name": "temperature"}]
931 
933  """Return True if properties asynchronously reported."""
934  return True
935 
936  def properties_retrievable(self) -> bool:
937  """Return True if properties can be retrieved."""
938  return True
939 
940  def get_property(self, name: str) -> Any:
941  """Read and return a property."""
942  if name != "temperature":
943  raise UnsupportedProperty(name)
944 
945  unit: str = self.entityentity.attributes.get(
946  ATTR_UNIT_OF_MEASUREMENT, self.hasshass.config.units.temperature_unit
947  )
948  temp: str | None = self.entityentity.state
949  if self.entityentity.domain == climate.DOMAIN:
950  unit = self.hasshass.config.units.temperature_unit
951  temp = self.entityentity.attributes.get(climate.ATTR_CURRENT_TEMPERATURE)
952  elif self.entityentity.domain == water_heater.DOMAIN:
953  unit = self.hasshass.config.units.temperature_unit
954  temp = self.entityentity.attributes.get(water_heater.ATTR_CURRENT_TEMPERATURE)
955 
956  if temp is None or temp in (STATE_UNAVAILABLE, STATE_UNKNOWN):
957  return None
958 
959  try:
960  temp_float = float(temp)
961  except ValueError:
962  _LOGGER.warning("Invalid temp value %s for %s", temp, self.entityentity.entity_id)
963  return None
964 
965  # Alexa displays temperatures with one decimal digit, we don't need to do
966  # rounding for presentation here.
967  return {"value": temp_float, "scale": API_TEMP_UNITS[UnitOfTemperature(unit)]}
968 
969 
971  """Implements Alexa.ContactSensor.
972 
973  The Alexa.ContactSensor interface describes the properties and events used
974  to report the state of an endpoint that detects contact between two
975  surfaces. For example, a contact sensor can report whether a door or window
976  is open.
977 
978  https://developer.amazon.com/docs/device-apis/alexa-contactsensor.html
979  """
980 
981  supported_locales = {
982  "de-DE",
983  "en-AU",
984  "en-CA",
985  "en-GB",
986  "en-IN",
987  "en-US",
988  "es-ES",
989  "es-MX",
990  "es-US",
991  "fr-CA",
992  "fr-FR",
993  "hi-IN",
994  "it-IT",
995  "ja-JP",
996  "pt-BR",
997  }
998 
999  def __init__(self, hass: HomeAssistant, entity: State) -> None:
1000  """Initialize the entity."""
1001  super().__init__(entity)
1002  self.hasshass = hass
1003 
1004  def name(self) -> str:
1005  """Return the Alexa API name of this interface."""
1006  return "Alexa.ContactSensor"
1007 
1008  def properties_supported(self) -> list[dict[str, str]]:
1009  """Return what properties this entity supports."""
1010  return [{"name": "detectionState"}]
1011 
1013  """Return True if properties asynchronously reported."""
1014  return True
1015 
1016  def properties_retrievable(self) -> bool:
1017  """Return True if properties can be retrieved."""
1018  return True
1019 
1020  def get_property(self, name: str) -> Any:
1021  """Read and return a property."""
1022  if name != "detectionState":
1023  raise UnsupportedProperty(name)
1024 
1025  if self.entityentity.state == STATE_ON:
1026  return "DETECTED"
1027  return "NOT_DETECTED"
1028 
1029 
1031  """Implements Alexa.MotionSensor.
1032 
1033  https://developer.amazon.com/docs/device-apis/alexa-motionsensor.html
1034  """
1035 
1036  supported_locales = {
1037  "de-DE",
1038  "en-AU",
1039  "en-CA",
1040  "en-GB",
1041  "en-IN",
1042  "en-US",
1043  "es-ES",
1044  "es-MX",
1045  "es-US",
1046  "fr-CA",
1047  "fr-FR",
1048  "hi-IN",
1049  "it-IT",
1050  "ja-JP",
1051  "pt-BR",
1052  }
1053 
1054  def __init__(self, hass: HomeAssistant, entity: State) -> None:
1055  """Initialize the entity."""
1056  super().__init__(entity)
1057  self.hasshass = hass
1058 
1059  def name(self) -> str:
1060  """Return the Alexa API name of this interface."""
1061  return "Alexa.MotionSensor"
1062 
1063  def properties_supported(self) -> list[dict[str, str]]:
1064  """Return what properties this entity supports."""
1065  return [{"name": "detectionState"}]
1066 
1068  """Return True if properties asynchronously reported."""
1069  return True
1070 
1071  def properties_retrievable(self) -> bool:
1072  """Return True if properties can be retrieved."""
1073  return True
1074 
1075  def get_property(self, name: str) -> Any:
1076  """Read and return a property."""
1077  if name != "detectionState":
1078  raise UnsupportedProperty(name)
1079 
1080  if self.entityentity.state == STATE_ON:
1081  return "DETECTED"
1082  return "NOT_DETECTED"
1083 
1084 
1086  """Implements Alexa.ThermostatController.
1087 
1088  https://developer.amazon.com/docs/device-apis/alexa-thermostatcontroller.html
1089  """
1090 
1091  supported_locales = {
1092  "ar-SA",
1093  "de-DE",
1094  "en-AU",
1095  "en-CA",
1096  "en-GB",
1097  "en-IN",
1098  "en-US",
1099  "es-ES",
1100  "es-MX",
1101  "es-US",
1102  "fr-CA",
1103  "fr-FR",
1104  "hi-IN",
1105  "it-IT",
1106  "ja-JP",
1107  "pt-BR",
1108  }
1109 
1110  def __init__(self, hass: HomeAssistant, entity: State) -> None:
1111  """Initialize the entity."""
1112  super().__init__(entity)
1113  self.hasshass = hass
1114 
1115  def name(self) -> str:
1116  """Return the Alexa API name of this interface."""
1117  return "Alexa.ThermostatController"
1118 
1119  def properties_supported(self) -> list[dict[str, str]]:
1120  """Return what properties this entity supports."""
1121  properties = [{"name": "thermostatMode"}]
1122  supported = self.entityentity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
1123  if self.entityentity.domain == climate.DOMAIN:
1124  if supported & climate.ClimateEntityFeature.TARGET_TEMPERATURE_RANGE:
1125  properties.append({"name": "lowerSetpoint"})
1126  properties.append({"name": "upperSetpoint"})
1127  if supported & climate.ClimateEntityFeature.TARGET_TEMPERATURE:
1128  properties.append({"name": "targetSetpoint"})
1129  elif (
1130  self.entityentity.domain == water_heater.DOMAIN
1131  and supported & water_heater.WaterHeaterEntityFeature.TARGET_TEMPERATURE
1132  ):
1133  properties.append({"name": "targetSetpoint"})
1134  return properties
1135 
1137  """Return True if properties asynchronously reported."""
1138  return True
1139 
1140  def properties_retrievable(self) -> bool:
1141  """Return True if properties can be retrieved."""
1142  return True
1143 
1144  def get_property(self, name: str) -> Any:
1145  """Read and return a property."""
1146  if self.entityentity.state == STATE_UNAVAILABLE:
1147  return None
1148 
1149  if name == "thermostatMode":
1150  if self.entityentity.domain == water_heater.DOMAIN:
1151  return None
1152  preset = self.entityentity.attributes.get(climate.ATTR_PRESET_MODE)
1153 
1154  mode: dict[str, str] | str | None
1155  if preset in API_THERMOSTAT_PRESETS:
1156  mode = API_THERMOSTAT_PRESETS[preset]
1157  elif self.entityentity.state == STATE_UNKNOWN:
1158  return None
1159  else:
1160  if self.entityentity.state not in API_THERMOSTAT_MODES:
1161  _LOGGER.error(
1162  "%s (%s) has unsupported state value '%s'",
1163  self.entityentity.entity_id,
1164  type(self.entityentity),
1165  self.entityentity.state,
1166  )
1167  raise UnsupportedProperty(name)
1168  mode = API_THERMOSTAT_MODES[HVACMode(self.entityentity.state)]
1169  return mode
1170 
1171  unit = self.hasshass.config.units.temperature_unit
1172  if name == "targetSetpoint":
1173  temp = self.entityentity.attributes.get(ATTR_TEMPERATURE)
1174  elif name == "lowerSetpoint":
1175  temp = self.entityentity.attributes.get(climate.ATTR_TARGET_TEMP_LOW)
1176  elif name == "upperSetpoint":
1177  temp = self.entityentity.attributes.get(climate.ATTR_TARGET_TEMP_HIGH)
1178  else:
1179  raise UnsupportedProperty(name)
1180 
1181  if temp is None:
1182  return None
1183 
1184  try:
1185  temp = float(temp)
1186  except ValueError:
1187  _LOGGER.warning(
1188  "Invalid temp value %s for %s in %s", temp, name, self.entityentity.entity_id
1189  )
1190  return None
1191 
1192  return {"value": temp, "scale": API_TEMP_UNITS[unit]}
1193 
1194  def configuration(self) -> dict[str, Any] | None:
1195  """Return configuration object.
1196 
1197  Translates climate HVAC_MODES and PRESETS to supported Alexa
1198  ThermostatMode Values.
1199 
1200  ThermostatMode Value must be AUTO, COOL, HEAT, ECO, OFF, or CUSTOM.
1201  Water heater devices do not return thermostat modes.
1202  """
1203  if self.entityentity.domain == water_heater.DOMAIN:
1204  return None
1205 
1206  hvac_modes = self.entityentity.attributes.get(climate.ATTR_HVAC_MODES) or []
1207  supported_modes: list[str] = [
1208  API_THERMOSTAT_MODES[mode]
1209  for mode in hvac_modes
1210  if mode in API_THERMOSTAT_MODES
1211  ]
1212 
1213  preset_modes = self.entityentity.attributes.get(climate.ATTR_PRESET_MODES)
1214  if preset_modes:
1215  for mode in preset_modes:
1216  thermostat_mode = API_THERMOSTAT_PRESETS.get(mode)
1217  if thermostat_mode:
1218  supported_modes.append(thermostat_mode)
1219 
1220  # Return False for supportsScheduling until supported with event
1221  # listener in handler.
1222  configuration: dict[str, Any] = {"supportsScheduling": False}
1223 
1224  if supported_modes:
1225  configuration["supportedModes"] = supported_modes
1226 
1227  return configuration
1228 
1229 
1231  """Implements Alexa.PowerLevelController.
1232 
1233  https://developer.amazon.com/docs/device-apis/alexa-powerlevelcontroller.html
1234  """
1235 
1236  supported_locales = {
1237  "de-DE",
1238  "en-AU",
1239  "en-CA",
1240  "en-GB",
1241  "en-IN",
1242  "en-US",
1243  "es-ES",
1244  "es-MX",
1245  "fr-CA",
1246  "fr-FR",
1247  "it-IT",
1248  "ja-JP",
1249  }
1250 
1251  def name(self) -> str:
1252  """Return the Alexa API name of this interface."""
1253  return "Alexa.PowerLevelController"
1254 
1255  def properties_supported(self) -> list[dict[str, str]]:
1256  """Return what properties this entity supports."""
1257  return [{"name": "powerLevel"}]
1258 
1260  """Return True if properties asynchronously reported."""
1261  return True
1262 
1263  def properties_retrievable(self) -> bool:
1264  """Return True if properties can be retrieved."""
1265  return True
1266 
1267  def get_property(self, name: str) -> Any:
1268  """Read and return a property."""
1269  if name != "powerLevel":
1270  raise UnsupportedProperty(name)
1271 
1272 
1274  """Implements Alexa.SecurityPanelController.
1275 
1276  https://developer.amazon.com/docs/device-apis/alexa-securitypanelcontroller.html
1277  """
1278 
1279  supported_locales = {
1280  "de-DE",
1281  "en-AU",
1282  "en-CA",
1283  "en-GB",
1284  "en-IN",
1285  "en-US",
1286  "es-ES",
1287  "es-MX",
1288  "es-US",
1289  "fr-CA",
1290  "fr-FR",
1291  "it-IT",
1292  "ja-JP",
1293  "pt-BR",
1294  }
1295 
1296  def __init__(self, hass: HomeAssistant, entity: State) -> None:
1297  """Initialize the entity."""
1298  super().__init__(entity)
1299  self.hasshass = hass
1300 
1301  def name(self) -> str:
1302  """Return the Alexa API name of this interface."""
1303  return "Alexa.SecurityPanelController"
1304 
1305  def properties_supported(self) -> list[dict[str, str]]:
1306  """Return what properties this entity supports."""
1307  return [{"name": "armState"}]
1308 
1310  """Return True if properties asynchronously reported."""
1311  return True
1312 
1313  def properties_retrievable(self) -> bool:
1314  """Return True if properties can be retrieved."""
1315  return True
1316 
1317  def get_property(self, name: str) -> Any:
1318  """Read and return a property."""
1319  if name != "armState":
1320  raise UnsupportedProperty(name)
1321 
1322  arm_state = self.entityentity.state
1323  if arm_state == AlarmControlPanelState.ARMED_HOME:
1324  return "ARMED_STAY"
1325  if arm_state == AlarmControlPanelState.ARMED_AWAY:
1326  return "ARMED_AWAY"
1327  if arm_state == AlarmControlPanelState.ARMED_NIGHT:
1328  return "ARMED_NIGHT"
1329  if arm_state == AlarmControlPanelState.ARMED_CUSTOM_BYPASS:
1330  return "ARMED_STAY"
1331  return "DISARMED"
1332 
1333  def configuration(self) -> dict[str, Any] | None:
1334  """Return configuration object with supported authorization types."""
1335  code_format = self.entityentity.attributes.get(ATTR_CODE_FORMAT)
1336  supported = self.entityentity.attributes[ATTR_SUPPORTED_FEATURES]
1337  configuration = {}
1338 
1339  supported_arm_states = [{"value": "DISARMED"}]
1340  if supported & AlarmControlPanelEntityFeature.ARM_AWAY:
1341  supported_arm_states.append({"value": "ARMED_AWAY"})
1342  if supported & AlarmControlPanelEntityFeature.ARM_HOME:
1343  supported_arm_states.append({"value": "ARMED_STAY"})
1344  if supported & AlarmControlPanelEntityFeature.ARM_NIGHT:
1345  supported_arm_states.append({"value": "ARMED_NIGHT"})
1346 
1347  configuration["supportedArmStates"] = supported_arm_states
1348 
1349  if code_format == CodeFormat.NUMBER:
1350  configuration["supportedAuthorizationTypes"] = [{"type": "FOUR_DIGIT_PIN"}]
1351 
1352  return configuration
1353 
1354 
1356  """Implements Alexa.ModeController.
1357 
1358  The instance property must be unique across ModeController, RangeController,
1359  ToggleController within the same device.
1360 
1361  The instance property should be a concatenated string of device domain period
1362  and single word. e.g. fan.speed & fan.direction.
1363 
1364  The instance property must not contain words from other instance property
1365  strings within the same device. e.g. Instance property cover.position &
1366  cover.tilt_position will cause the Alexa.Discovery directive to fail.
1367 
1368  An instance property string value may be reused for different devices.
1369 
1370  https://developer.amazon.com/docs/device-apis/alexa-modecontroller.html
1371  """
1372 
1373  supported_locales = {
1374  "de-DE",
1375  "en-AU",
1376  "en-CA",
1377  "en-GB",
1378  "en-IN",
1379  "en-US",
1380  "es-ES",
1381  "es-MX",
1382  "es-US",
1383  "fr-CA",
1384  "fr-FR",
1385  "hi-IN",
1386  "it-IT",
1387  "ja-JP",
1388  "pt-BR",
1389  }
1390 
1392  self, entity: State, instance: str, non_controllable: bool = False
1393  ) -> None:
1394  """Initialize the entity."""
1395  AlexaCapability.__init__(self, entity, instance, non_controllable)
1396  self._resource_resource = None
1397  self._semantics_semantics = None
1398 
1399  def name(self) -> str:
1400  """Return the Alexa API name of this interface."""
1401  return "Alexa.ModeController"
1402 
1403  def properties_supported(self) -> list[dict[str, str]]:
1404  """Return what properties this entity supports."""
1405  return [{"name": "mode"}]
1406 
1408  """Return True if properties asynchronously reported."""
1409  return True
1410 
1411  def properties_retrievable(self) -> bool:
1412  """Return True if properties can be retrieved."""
1413  return True
1414 
1415  def get_property(self, name: str) -> Any:
1416  """Read and return a property."""
1417  if name != "mode":
1418  raise UnsupportedProperty(name)
1419 
1420  # Fan Direction
1421  if self.instanceinstanceinstance == f"{fan.DOMAIN}.{fan.ATTR_DIRECTION}":
1422  mode = self.entityentity.attributes.get(fan.ATTR_DIRECTION, None)
1423  if mode in (fan.DIRECTION_FORWARD, fan.DIRECTION_REVERSE, STATE_UNKNOWN):
1424  return f"{fan.ATTR_DIRECTION}.{mode}"
1425 
1426  # Fan preset_mode
1427  if self.instanceinstanceinstance == f"{fan.DOMAIN}.{fan.ATTR_PRESET_MODE}":
1428  mode = self.entityentity.attributes.get(fan.ATTR_PRESET_MODE, None)
1429  if mode in self.entityentity.attributes.get(fan.ATTR_PRESET_MODES, None):
1430  return f"{fan.ATTR_PRESET_MODE}.{mode}"
1431 
1432  # Humidifier mode
1433  if self.instanceinstanceinstance == f"{humidifier.DOMAIN}.{humidifier.ATTR_MODE}":
1434  mode = self.entityentity.attributes.get(humidifier.ATTR_MODE)
1435  modes: list[str] = (
1436  self.entityentity.attributes.get(humidifier.ATTR_AVAILABLE_MODES) or []
1437  )
1438  if mode in modes:
1439  return f"{humidifier.ATTR_MODE}.{mode}"
1440 
1441  # Remote Activity
1442  if self.instanceinstanceinstance == f"{remote.DOMAIN}.{remote.ATTR_ACTIVITY}":
1443  activity = self.entityentity.attributes.get(remote.ATTR_CURRENT_ACTIVITY, None)
1444  if activity in self.entityentity.attributes.get(remote.ATTR_ACTIVITY_LIST, []):
1445  return f"{remote.ATTR_ACTIVITY}.{activity}"
1446 
1447  # Water heater operation mode
1448  if self.instanceinstanceinstance == f"{water_heater.DOMAIN}.{water_heater.ATTR_OPERATION_MODE}":
1449  operation_mode = self.entityentity.attributes.get(
1450  water_heater.ATTR_OPERATION_MODE
1451  )
1452  operation_modes: list[str] = (
1453  self.entityentity.attributes.get(water_heater.ATTR_OPERATION_LIST) or []
1454  )
1455  if operation_mode in operation_modes:
1456  return f"{water_heater.ATTR_OPERATION_MODE}.{operation_mode}"
1457 
1458  # Cover Position
1459  if self.instanceinstanceinstance == f"{cover.DOMAIN}.{cover.ATTR_POSITION}":
1460  # Return state instead of position when using ModeController.
1461  mode = self.entityentity.state
1462  if mode in (
1463  cover.STATE_OPEN,
1464  cover.STATE_OPENING,
1465  cover.STATE_CLOSED,
1466  cover.STATE_CLOSING,
1467  STATE_UNKNOWN,
1468  ):
1469  return f"{cover.ATTR_POSITION}.{mode}"
1470 
1471  # Valve position state
1472  if self.instanceinstanceinstance == f"{valve.DOMAIN}.state":
1473  # Return state instead of position when using ModeController.
1474  state = self.entityentity.state
1475  if state in (
1476  valve.STATE_OPEN,
1477  valve.STATE_OPENING,
1478  valve.STATE_CLOSED,
1479  valve.STATE_CLOSING,
1480  STATE_UNKNOWN,
1481  ):
1482  return f"state.{state}"
1483 
1484  return None
1485 
1486  def configuration(self) -> dict[str, Any] | None:
1487  """Return configuration with modeResources."""
1488  if isinstance(self._resource_resource, AlexaCapabilityResource):
1489  return self._resource_resource.serialize_configuration()
1490 
1491  return None
1492 
1493  def capability_resources(self) -> dict[str, list[dict[str, Any]]]:
1494  """Return capabilityResources object."""
1495 
1496  # Fan Direction Resource
1497  if self.instanceinstanceinstance == f"{fan.DOMAIN}.{fan.ATTR_DIRECTION}":
1498  self._resource_resource = AlexaModeResource(
1499  [AlexaGlobalCatalog.SETTING_DIRECTION], False
1500  )
1501  self._resource_resource.add_mode(
1502  f"{fan.ATTR_DIRECTION}.{fan.DIRECTION_FORWARD}", [fan.DIRECTION_FORWARD]
1503  )
1504  self._resource_resource.add_mode(
1505  f"{fan.ATTR_DIRECTION}.{fan.DIRECTION_REVERSE}", [fan.DIRECTION_REVERSE]
1506  )
1507  return self._resource_resource.serialize_capability_resources()
1508 
1509  # Fan preset_mode
1510  if self.instanceinstanceinstance == f"{fan.DOMAIN}.{fan.ATTR_PRESET_MODE}":
1511  self._resource_resource = AlexaModeResource(
1512  [AlexaGlobalCatalog.SETTING_PRESET], False
1513  )
1514  preset_modes = self.entityentity.attributes.get(fan.ATTR_PRESET_MODES) or []
1515  for preset_mode in preset_modes:
1516  self._resource_resource.add_mode(
1517  f"{fan.ATTR_PRESET_MODE}.{preset_mode}", [preset_mode]
1518  )
1519  # Fans with a single preset_mode completely break Alexa discovery, add a
1520  # fake preset (see issue #53832).
1521  if len(preset_modes) == 1:
1522  self._resource_resource.add_mode(
1523  f"{fan.ATTR_PRESET_MODE}.{PRESET_MODE_NA}", [PRESET_MODE_NA]
1524  )
1525  return self._resource_resource.serialize_capability_resources()
1526 
1527  # Humidifier modes
1528  if self.instanceinstanceinstance == f"{humidifier.DOMAIN}.{humidifier.ATTR_MODE}":
1529  self._resource_resource = AlexaModeResource([AlexaGlobalCatalog.SETTING_MODE], False)
1530  modes = self.entityentity.attributes.get(humidifier.ATTR_AVAILABLE_MODES) or []
1531  for mode in modes:
1532  self._resource_resource.add_mode(f"{humidifier.ATTR_MODE}.{mode}", [mode])
1533  # Humidifiers or Fans with a single mode completely break Alexa discovery,
1534  # add a fake preset (see issue #53832).
1535  if len(modes) == 1:
1536  self._resource_resource.add_mode(
1537  f"{humidifier.ATTR_MODE}.{PRESET_MODE_NA}", [PRESET_MODE_NA]
1538  )
1539  return self._resource_resource.serialize_capability_resources()
1540 
1541  # Water heater operation modes
1542  if self.instanceinstanceinstance == f"{water_heater.DOMAIN}.{water_heater.ATTR_OPERATION_MODE}":
1543  self._resource_resource = AlexaModeResource([AlexaGlobalCatalog.SETTING_MODE], False)
1544  operation_modes = (
1545  self.entityentity.attributes.get(water_heater.ATTR_OPERATION_LIST) or []
1546  )
1547  for operation_mode in operation_modes:
1548  self._resource_resource.add_mode(
1549  f"{water_heater.ATTR_OPERATION_MODE}.{operation_mode}",
1550  [operation_mode],
1551  )
1552  # Devices with a single mode completely break Alexa discovery,
1553  # add a fake preset (see issue #53832).
1554  if len(operation_modes) == 1:
1555  self._resource_resource.add_mode(
1556  f"{water_heater.ATTR_OPERATION_MODE}.{PRESET_MODE_NA}",
1557  [PRESET_MODE_NA],
1558  )
1559  return self._resource_resource.serialize_capability_resources()
1560 
1561  # Remote Resource
1562  if self.instanceinstanceinstance == f"{remote.DOMAIN}.{remote.ATTR_ACTIVITY}":
1563  # Use the mode controller for a remote because the input controller
1564  # only allows a preset of names as an input.
1565  self._resource_resource = AlexaModeResource([AlexaGlobalCatalog.SETTING_MODE], False)
1566  activities = self.entityentity.attributes.get(remote.ATTR_ACTIVITY_LIST) or []
1567  for activity in activities:
1568  self._resource_resource.add_mode(
1569  f"{remote.ATTR_ACTIVITY}.{activity}", [activity]
1570  )
1571  # Remotes with a single activity completely break Alexa discovery, add a
1572  # fake activity to the mode controller (see issue #53832).
1573  if len(activities) == 1:
1574  self._resource_resource.add_mode(
1575  f"{remote.ATTR_ACTIVITY}.{PRESET_MODE_NA}", [PRESET_MODE_NA]
1576  )
1577  return self._resource_resource.serialize_capability_resources()
1578 
1579  # Cover Position Resources
1580  if self.instanceinstanceinstance == f"{cover.DOMAIN}.{cover.ATTR_POSITION}":
1581  self._resource_resource = AlexaModeResource(
1582  ["Position", AlexaGlobalCatalog.SETTING_OPENING], False
1583  )
1584  self._resource_resource.add_mode(
1585  f"{cover.ATTR_POSITION}.{cover.STATE_OPEN}",
1586  [AlexaGlobalCatalog.VALUE_OPEN],
1587  )
1588  self._resource_resource.add_mode(
1589  f"{cover.ATTR_POSITION}.{cover.STATE_CLOSED}",
1590  [AlexaGlobalCatalog.VALUE_CLOSE],
1591  )
1592  self._resource_resource.add_mode(
1593  f"{cover.ATTR_POSITION}.custom",
1594  ["Custom", AlexaGlobalCatalog.SETTING_PRESET],
1595  )
1596  return self._resource_resource.serialize_capability_resources()
1597 
1598  # Valve position resources
1599  if self.instanceinstanceinstance == f"{valve.DOMAIN}.state":
1600  supported_features = self.entityentity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
1601  self._resource_resource = AlexaModeResource(
1602  ["Preset", AlexaGlobalCatalog.SETTING_PRESET], False
1603  )
1604  modes = 0
1605  if supported_features & valve.ValveEntityFeature.OPEN:
1606  self._resource_resource.add_mode(
1607  f"state.{valve.STATE_OPEN}",
1608  ["Open", AlexaGlobalCatalog.SETTING_PRESET],
1609  )
1610  modes += 1
1611  if supported_features & valve.ValveEntityFeature.CLOSE:
1612  self._resource_resource.add_mode(
1613  f"state.{valve.STATE_CLOSED}",
1614  ["Closed", AlexaGlobalCatalog.SETTING_PRESET],
1615  )
1616  modes += 1
1617 
1618  # Alexa requires at least 2 modes
1619  if modes == 1:
1620  self._resource_resource.add_mode(f"state.{PRESET_MODE_NA}", [PRESET_MODE_NA])
1621 
1622  return self._resource_resource.serialize_capability_resources()
1623 
1624  return {}
1625 
1626  def semantics(self) -> dict[str, Any] | None:
1627  """Build and return semantics object."""
1628  supported = self.entityentity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
1629 
1630  # Cover Position
1631  if self.instanceinstanceinstance == f"{cover.DOMAIN}.{cover.ATTR_POSITION}":
1632  lower_labels = [AlexaSemantics.ACTION_LOWER]
1633  raise_labels = [AlexaSemantics.ACTION_RAISE]
1634  self._semantics_semantics = AlexaSemantics()
1635 
1636  # Add open/close semantics if tilt is not supported.
1637  if not supported & cover.CoverEntityFeature.SET_TILT_POSITION:
1638  lower_labels.append(AlexaSemantics.ACTION_CLOSE)
1639  raise_labels.append(AlexaSemantics.ACTION_OPEN)
1640  self._semantics_semantics.add_states_to_value(
1641  [AlexaSemantics.STATES_CLOSED],
1642  f"{cover.ATTR_POSITION}.{cover.STATE_CLOSED}",
1643  )
1644  self._semantics_semantics.add_states_to_value(
1645  [AlexaSemantics.STATES_OPEN],
1646  f"{cover.ATTR_POSITION}.{cover.STATE_OPEN}",
1647  )
1648 
1649  self._semantics_semantics.add_action_to_directive(
1650  lower_labels,
1651  "SetMode",
1652  {"mode": f"{cover.ATTR_POSITION}.{cover.STATE_CLOSED}"},
1653  )
1654  self._semantics_semantics.add_action_to_directive(
1655  raise_labels,
1656  "SetMode",
1657  {"mode": f"{cover.ATTR_POSITION}.{cover.STATE_OPEN}"},
1658  )
1659 
1660  return self._semantics_semantics.serialize_semantics()
1661 
1662  # Valve Position
1663  if self.instanceinstanceinstance == f"{valve.DOMAIN}.state":
1664  close_labels = [AlexaSemantics.ACTION_CLOSE]
1665  open_labels = [AlexaSemantics.ACTION_OPEN]
1666  self._semantics_semantics = AlexaSemantics()
1667 
1668  self._semantics_semantics.add_states_to_value(
1669  [AlexaSemantics.STATES_CLOSED],
1670  f"state.{valve.STATE_CLOSED}",
1671  )
1672  self._semantics_semantics.add_states_to_value(
1673  [AlexaSemantics.STATES_OPEN],
1674  f"state.{valve.STATE_OPEN}",
1675  )
1676 
1677  self._semantics_semantics.add_action_to_directive(
1678  close_labels,
1679  "SetMode",
1680  {"mode": f"state.{valve.STATE_CLOSED}"},
1681  )
1682  self._semantics_semantics.add_action_to_directive(
1683  open_labels,
1684  "SetMode",
1685  {"mode": f"state.{valve.STATE_OPEN}"},
1686  )
1687 
1688  return self._semantics_semantics.serialize_semantics()
1689 
1690  return None
1691 
1692 
1694  """Implements Alexa.RangeController.
1695 
1696  The instance property must be unique across ModeController, RangeController,
1697  ToggleController within the same device.
1698 
1699  The instance property should be a concatenated string of device domain period
1700  and single word. e.g. fan.speed & fan.direction.
1701 
1702  The instance property must not contain words from other instance property
1703  strings within the same device. e.g. Instance property cover.position &
1704  cover.tilt_position will cause the Alexa.Discovery directive to fail.
1705 
1706  An instance property string value may be reused for different devices.
1707 
1708  https://developer.amazon.com/docs/device-apis/alexa-rangecontroller.html
1709  """
1710 
1711  supported_locales = {
1712  "de-DE",
1713  "en-AU",
1714  "en-CA",
1715  "en-GB",
1716  "en-IN",
1717  "en-US",
1718  "es-ES",
1719  "es-MX",
1720  "es-US",
1721  "fr-CA",
1722  "fr-FR",
1723  "hi-IN",
1724  "it-IT",
1725  "ja-JP",
1726  "pt-BR",
1727  }
1728 
1730  self, entity: State, instance: str | None, non_controllable: bool = False
1731  ) -> None:
1732  """Initialize the entity."""
1733  AlexaCapability.__init__(self, entity, instance, non_controllable)
1734  self._resource_resource = None
1735  self._semantics_semantics = None
1736 
1737  def name(self) -> str:
1738  """Return the Alexa API name of this interface."""
1739  return "Alexa.RangeController"
1740 
1741  def properties_supported(self) -> list[dict[str, str]]:
1742  """Return what properties this entity supports."""
1743  return [{"name": "rangeValue"}]
1744 
1746  """Return True if properties asynchronously reported."""
1747  return True
1748 
1749  def properties_retrievable(self) -> bool:
1750  """Return True if properties can be retrieved."""
1751  return True
1752 
1753  def get_property(self, name: str) -> Any:
1754  """Read and return a property."""
1755  if name != "rangeValue":
1756  raise UnsupportedProperty(name)
1757 
1758  # Return None for unavailable and unknown states.
1759  # Allows the Alexa.EndpointHealth Interface to handle the unavailable
1760  # state in a stateReport.
1761  if self.entityentity.state in (STATE_UNAVAILABLE, STATE_UNKNOWN, None):
1762  return None
1763 
1764  # Cover Position
1765  if self.instanceinstanceinstance == f"{cover.DOMAIN}.{cover.ATTR_POSITION}":
1766  return self.entityentity.attributes.get(cover.ATTR_CURRENT_POSITION)
1767 
1768  # Cover Tilt
1769  if self.instanceinstanceinstance == f"{cover.DOMAIN}.tilt":
1770  return self.entityentity.attributes.get(cover.ATTR_CURRENT_TILT_POSITION)
1771 
1772  # Fan speed percentage
1773  if self.instanceinstanceinstance == f"{fan.DOMAIN}.{fan.ATTR_PERCENTAGE}":
1774  supported = self.entityentity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
1775  if supported and fan.FanEntityFeature.SET_SPEED:
1776  return self.entityentity.attributes.get(fan.ATTR_PERCENTAGE)
1777  return 100 if self.entityentity.state == fan.STATE_ON else 0
1778 
1779  # Humidifier target humidity
1780  if self.instanceinstanceinstance == f"{humidifier.DOMAIN}.{humidifier.ATTR_HUMIDITY}":
1781  # If the humidifier is turned off the target humidity attribute is not set.
1782  # We return 0 to make clear we do not know the current value.
1783  return self.entityentity.attributes.get(humidifier.ATTR_HUMIDITY, 0)
1784 
1785  # Input Number Value
1786  if self.instanceinstanceinstance == f"{input_number.DOMAIN}.{input_number.ATTR_VALUE}":
1787  return float(self.entityentity.state)
1788 
1789  # Number Value
1790  if self.instanceinstanceinstance == f"{number.DOMAIN}.{number.ATTR_VALUE}":
1791  return float(self.entityentity.state)
1792 
1793  # Vacuum Fan Speed
1794  if self.instanceinstanceinstance == f"{vacuum.DOMAIN}.{vacuum.ATTR_FAN_SPEED}":
1795  speed_list = self.entityentity.attributes.get(vacuum.ATTR_FAN_SPEED_LIST)
1796  speed = self.entityentity.attributes.get(vacuum.ATTR_FAN_SPEED)
1797  if speed_list is not None and speed is not None:
1798  return next((i for i, v in enumerate(speed_list) if v == speed), None)
1799 
1800  # Valve Position
1801  if self.instanceinstanceinstance == f"{valve.DOMAIN}.{valve.ATTR_POSITION}":
1802  return self.entityentity.attributes.get(valve.ATTR_CURRENT_POSITION)
1803 
1804  return None
1805 
1806  def configuration(self) -> dict[str, Any] | None:
1807  """Return configuration with presetResources."""
1808  if isinstance(self._resource_resource, AlexaCapabilityResource):
1809  return self._resource_resource.serialize_configuration()
1810 
1811  return None
1812 
1813  def capability_resources(self) -> dict[str, list[dict[str, Any]]]:
1814  """Return capabilityResources object."""
1815 
1816  # Fan Speed Percentage Resources
1817  if self.instanceinstanceinstance == f"{fan.DOMAIN}.{fan.ATTR_PERCENTAGE}":
1818  percentage_step = self.entityentity.attributes.get(fan.ATTR_PERCENTAGE_STEP)
1819  self._resource_resource = AlexaPresetResource(
1820  labels=["Percentage", AlexaGlobalCatalog.SETTING_FAN_SPEED],
1821  min_value=0,
1822  max_value=100,
1823  # precision must be a divider of 100 and must be an integer; set step
1824  # size to 1 for a consistent behavior except for on/off fans
1825  precision=1 if percentage_step else 100,
1826  unit=AlexaGlobalCatalog.UNIT_PERCENT,
1827  )
1828  return self._resource_resource.serialize_capability_resources()
1829 
1830  # Humidifier Target Humidity Resources
1831  if self.instanceinstanceinstance == f"{humidifier.DOMAIN}.{humidifier.ATTR_HUMIDITY}":
1832  self._resource_resource = AlexaPresetResource(
1833  labels=["Humidity", "Percentage", "Target humidity"],
1834  min_value=self.entityentity.attributes.get(humidifier.ATTR_MIN_HUMIDITY, 10),
1835  max_value=self.entityentity.attributes.get(humidifier.ATTR_MAX_HUMIDITY, 90),
1836  precision=1,
1837  unit=AlexaGlobalCatalog.UNIT_PERCENT,
1838  )
1839  return self._resource_resource.serialize_capability_resources()
1840 
1841  # Cover Position Resources
1842  if self.instanceinstanceinstance == f"{cover.DOMAIN}.{cover.ATTR_POSITION}":
1843  self._resource_resource = AlexaPresetResource(
1844  ["Position", AlexaGlobalCatalog.SETTING_OPENING],
1845  min_value=0,
1846  max_value=100,
1847  precision=1,
1848  unit=AlexaGlobalCatalog.UNIT_PERCENT,
1849  )
1850  return self._resource_resource.serialize_capability_resources()
1851 
1852  # Cover Tilt Resources
1853  if self.instanceinstanceinstance == f"{cover.DOMAIN}.tilt":
1854  self._resource_resource = AlexaPresetResource(
1855  ["Tilt", "Angle", AlexaGlobalCatalog.SETTING_DIRECTION],
1856  min_value=0,
1857  max_value=100,
1858  precision=1,
1859  unit=AlexaGlobalCatalog.UNIT_PERCENT,
1860  )
1861  return self._resource_resource.serialize_capability_resources()
1862 
1863  # Input Number Value
1864  if self.instanceinstanceinstance == f"{input_number.DOMAIN}.{input_number.ATTR_VALUE}":
1865  min_value = float(self.entityentity.attributes[input_number.ATTR_MIN])
1866  max_value = float(self.entityentity.attributes[input_number.ATTR_MAX])
1867  precision = float(self.entityentity.attributes.get(input_number.ATTR_STEP, 1))
1868  unit = self.entityentity.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
1869 
1870  self._resource_resource = AlexaPresetResource(
1871  ["Value", get_resource_by_unit_of_measurement(self.entityentity)],
1872  min_value=min_value,
1873  max_value=max_value,
1874  precision=precision,
1875  unit=unit,
1876  )
1877  self._resource_resource.add_preset(
1878  value=min_value, labels=[AlexaGlobalCatalog.VALUE_MINIMUM]
1879  )
1880  self._resource_resource.add_preset(
1881  value=max_value, labels=[AlexaGlobalCatalog.VALUE_MAXIMUM]
1882  )
1883  return self._resource_resource.serialize_capability_resources()
1884 
1885  # Number Value
1886  if self.instanceinstanceinstance == f"{number.DOMAIN}.{number.ATTR_VALUE}":
1887  min_value = float(self.entityentity.attributes[number.ATTR_MIN])
1888  max_value = float(self.entityentity.attributes[number.ATTR_MAX])
1889  precision = float(self.entityentity.attributes.get(number.ATTR_STEP, 1))
1890  unit = self.entityentity.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
1891 
1892  self._resource_resource = AlexaPresetResource(
1893  ["Value", get_resource_by_unit_of_measurement(self.entityentity)],
1894  min_value=min_value,
1895  max_value=max_value,
1896  precision=precision,
1897  unit=unit,
1898  )
1899  self._resource_resource.add_preset(
1900  value=min_value, labels=[AlexaGlobalCatalog.VALUE_MINIMUM]
1901  )
1902  self._resource_resource.add_preset(
1903  value=max_value, labels=[AlexaGlobalCatalog.VALUE_MAXIMUM]
1904  )
1905  return self._resource_resource.serialize_capability_resources()
1906 
1907  # Vacuum Fan Speed Resources
1908  if self.instanceinstanceinstance == f"{vacuum.DOMAIN}.{vacuum.ATTR_FAN_SPEED}":
1909  speed_list = self.entityentity.attributes[vacuum.ATTR_FAN_SPEED_LIST]
1910  max_value = len(speed_list) - 1
1911  self._resource_resource = AlexaPresetResource(
1912  labels=[AlexaGlobalCatalog.SETTING_FAN_SPEED],
1913  min_value=0,
1914  max_value=max_value,
1915  precision=1,
1916  )
1917  for index, speed in enumerate(speed_list):
1918  labels = [speed.replace("_", " ")]
1919  if index == 1:
1920  labels.append(AlexaGlobalCatalog.VALUE_MINIMUM)
1921  if index == max_value:
1922  labels.append(AlexaGlobalCatalog.VALUE_MAXIMUM)
1923  self._resource_resource.add_preset(value=index, labels=labels)
1924 
1925  return self._resource_resource.serialize_capability_resources()
1926 
1927  # Valve Position Resources
1928  if self.instanceinstanceinstance == f"{valve.DOMAIN}.{valve.ATTR_POSITION}":
1929  self._resource_resource = AlexaPresetResource(
1930  ["Opening", AlexaGlobalCatalog.SETTING_OPENING],
1931  min_value=0,
1932  max_value=100,
1933  precision=1,
1934  unit=AlexaGlobalCatalog.UNIT_PERCENT,
1935  )
1936  return self._resource_resource.serialize_capability_resources()
1937 
1938  return {}
1939 
1940  def semantics(self) -> dict[str, Any] | None:
1941  """Build and return semantics object."""
1942  supported = self.entityentity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
1943 
1944  # Cover Position
1945  if self.instanceinstanceinstance == f"{cover.DOMAIN}.{cover.ATTR_POSITION}":
1946  lower_labels = [AlexaSemantics.ACTION_LOWER]
1947  raise_labels = [AlexaSemantics.ACTION_RAISE]
1948  self._semantics_semantics = AlexaSemantics()
1949 
1950  # Add open/close semantics if tilt is not supported.
1951  if not supported & cover.CoverEntityFeature.SET_TILT_POSITION:
1952  lower_labels.append(AlexaSemantics.ACTION_CLOSE)
1953  raise_labels.append(AlexaSemantics.ACTION_OPEN)
1954  self._semantics_semantics.add_states_to_value(
1955  [AlexaSemantics.STATES_CLOSED], value=0
1956  )
1957  self._semantics_semantics.add_states_to_range(
1958  [AlexaSemantics.STATES_OPEN], min_value=1, max_value=100
1959  )
1960 
1961  self._semantics_semantics.add_action_to_directive(
1962  lower_labels, "SetRangeValue", {"rangeValue": 0}
1963  )
1964  self._semantics_semantics.add_action_to_directive(
1965  raise_labels, "SetRangeValue", {"rangeValue": 100}
1966  )
1967  return self._semantics_semantics.serialize_semantics()
1968 
1969  # Cover Tilt
1970  if self.instanceinstanceinstance == f"{cover.DOMAIN}.tilt":
1971  self._semantics_semantics = AlexaSemantics()
1972  self._semantics_semantics.add_action_to_directive(
1973  [AlexaSemantics.ACTION_CLOSE], "SetRangeValue", {"rangeValue": 0}
1974  )
1975  self._semantics_semantics.add_action_to_directive(
1976  [AlexaSemantics.ACTION_OPEN], "SetRangeValue", {"rangeValue": 100}
1977  )
1978  self._semantics_semantics.add_states_to_value([AlexaSemantics.STATES_CLOSED], value=0)
1979  self._semantics_semantics.add_states_to_range(
1980  [AlexaSemantics.STATES_OPEN], min_value=1, max_value=100
1981  )
1982  return self._semantics_semantics.serialize_semantics()
1983 
1984  # Fan Speed Percentage
1985  if self.instanceinstanceinstance == f"{fan.DOMAIN}.{fan.ATTR_PERCENTAGE}":
1986  lower_labels = [AlexaSemantics.ACTION_LOWER]
1987  raise_labels = [AlexaSemantics.ACTION_RAISE]
1988  self._semantics_semantics = AlexaSemantics()
1989 
1990  self._semantics_semantics.add_action_to_directive(
1991  lower_labels, "SetRangeValue", {"rangeValue": 0}
1992  )
1993  self._semantics_semantics.add_action_to_directive(
1994  raise_labels, "SetRangeValue", {"rangeValue": 100}
1995  )
1996  return self._semantics_semantics.serialize_semantics()
1997 
1998  # Target Humidity Percentage
1999  if self.instanceinstanceinstance == f"{humidifier.DOMAIN}.{humidifier.ATTR_HUMIDITY}":
2000  lower_labels = [AlexaSemantics.ACTION_LOWER]
2001  raise_labels = [AlexaSemantics.ACTION_RAISE]
2002  self._semantics_semantics = AlexaSemantics()
2003  min_value = self.entityentity.attributes.get(humidifier.ATTR_MIN_HUMIDITY, 10)
2004  max_value = self.entityentity.attributes.get(humidifier.ATTR_MAX_HUMIDITY, 90)
2005 
2006  self._semantics_semantics.add_action_to_directive(
2007  lower_labels, "SetRangeValue", {"rangeValue": min_value}
2008  )
2009  self._semantics_semantics.add_action_to_directive(
2010  raise_labels, "SetRangeValue", {"rangeValue": max_value}
2011  )
2012  return self._semantics_semantics.serialize_semantics()
2013 
2014  # Valve Position
2015  if self.instanceinstanceinstance == f"{valve.DOMAIN}.{valve.ATTR_POSITION}":
2016  close_labels = [AlexaSemantics.ACTION_CLOSE]
2017  open_labels = [AlexaSemantics.ACTION_OPEN]
2018  self._semantics_semantics = AlexaSemantics()
2019 
2020  self._semantics_semantics.add_states_to_value([AlexaSemantics.STATES_CLOSED], value=0)
2021  self._semantics_semantics.add_states_to_range(
2022  [AlexaSemantics.STATES_OPEN], min_value=1, max_value=100
2023  )
2024 
2025  self._semantics_semantics.add_action_to_directive(
2026  close_labels, "SetRangeValue", {"rangeValue": 0}
2027  )
2028  self._semantics_semantics.add_action_to_directive(
2029  open_labels, "SetRangeValue", {"rangeValue": 100}
2030  )
2031  return self._semantics_semantics.serialize_semantics()
2032 
2033  return None
2034 
2035 
2037  """Implements Alexa.ToggleController.
2038 
2039  The instance property must be unique across ModeController, RangeController,
2040  ToggleController within the same device.
2041 
2042  The instance property should be a concatenated string of device domain period
2043  and single word. e.g. fan.speed & fan.direction.
2044 
2045  The instance property must not contain words from other instance property
2046  strings within the same device. e.g. Instance property cover.position
2047  & cover.tilt_position will cause the Alexa.Discovery directive to fail.
2048 
2049  An instance property string value may be reused for different devices.
2050 
2051  https://developer.amazon.com/docs/device-apis/alexa-togglecontroller.html
2052  """
2053 
2054  supported_locales = {
2055  "de-DE",
2056  "en-AU",
2057  "en-CA",
2058  "en-GB",
2059  "en-IN",
2060  "en-US",
2061  "es-ES",
2062  "es-MX",
2063  "es-US",
2064  "fr-CA",
2065  "fr-FR",
2066  "hi-IN",
2067  "it-IT",
2068  "ja-JP",
2069  "pt-BR",
2070  }
2071 
2073  self, entity: State, instance: str, non_controllable: bool = False
2074  ) -> None:
2075  """Initialize the entity."""
2076  AlexaCapability.__init__(self, entity, instance, non_controllable)
2077  self._resource_resource = None
2078  self._semantics_semantics = None
2079 
2080  def name(self) -> str:
2081  """Return the Alexa API name of this interface."""
2082  return "Alexa.ToggleController"
2083 
2084  def properties_supported(self) -> list[dict[str, str]]:
2085  """Return what properties this entity supports."""
2086  return [{"name": "toggleState"}]
2087 
2089  """Return True if properties asynchronously reported."""
2090  return True
2091 
2092  def properties_retrievable(self) -> bool:
2093  """Return True if properties can be retrieved."""
2094  return True
2095 
2096  def get_property(self, name: str) -> Any:
2097  """Read and return a property."""
2098  if name != "toggleState":
2099  raise UnsupportedProperty(name)
2100 
2101  # Fan Oscillating
2102  if self.instanceinstanceinstance == f"{fan.DOMAIN}.{fan.ATTR_OSCILLATING}":
2103  is_on = bool(self.entityentity.attributes.get(fan.ATTR_OSCILLATING))
2104  return "ON" if is_on else "OFF"
2105 
2106  # Stop Valve
2107  if self.instanceinstanceinstance == f"{valve.DOMAIN}.stop":
2108  return "OFF"
2109 
2110  return None
2111 
2112  def capability_resources(self) -> dict[str, list[dict[str, Any]]]:
2113  """Return capabilityResources object."""
2114 
2115  # Fan Oscillating Resource
2116  if self.instanceinstanceinstance == f"{fan.DOMAIN}.{fan.ATTR_OSCILLATING}":
2117  self._resource_resource = AlexaCapabilityResource(
2118  [AlexaGlobalCatalog.SETTING_OSCILLATE, "Rotate", "Rotation"]
2119  )
2120  return self._resource_resource.serialize_capability_resources()
2121 
2122  if self.instanceinstanceinstance == f"{valve.DOMAIN}.stop":
2123  self._resource_resource = AlexaCapabilityResource(["Stop"])
2124  return self._resource_resource.serialize_capability_resources()
2125 
2126  return {}
2127 
2128 
2130  """Implements Alexa.ChannelController.
2131 
2132  https://developer.amazon.com/docs/device-apis/alexa-channelcontroller.html
2133  """
2134 
2135  supported_locales = {
2136  "ar-SA",
2137  "de-DE",
2138  "en-AU",
2139  "en-CA",
2140  "en-GB",
2141  "en-IN",
2142  "en-US",
2143  "es-ES",
2144  "es-MX",
2145  "es-US",
2146  "fr-CA",
2147  "fr-FR",
2148  "hi-IN",
2149  "it-IT",
2150  "ja-JP",
2151  "pt-BR",
2152  }
2153 
2154  def name(self) -> str:
2155  """Return the Alexa API name of this interface."""
2156  return "Alexa.ChannelController"
2157 
2158 
2160  """Implements Alexa.DoorbellEventSource.
2161 
2162  https://developer.amazon.com/docs/device-apis/alexa-doorbelleventsource.html
2163  """
2164 
2165  supported_locales = {
2166  "ar-SA",
2167  "de-DE",
2168  "en-AU",
2169  "en-CA",
2170  "en-GB",
2171  "en-IN",
2172  "en-US",
2173  "es-ES",
2174  "es-MX",
2175  "es-US",
2176  "fr-CA",
2177  "fr-FR",
2178  "hi-IN",
2179  "it-IT",
2180  "ja-JP",
2181  "pt-BR",
2182  }
2183 
2184  def name(self) -> str:
2185  """Return the Alexa API name of this interface."""
2186  return "Alexa.DoorbellEventSource"
2187 
2189  """Return True for proactively reported capability."""
2190  return True
2191 
2192 
2194  """Implements Alexa.PlaybackStateReporter.
2195 
2196  https://developer.amazon.com/docs/device-apis/alexa-playbackstatereporter.html
2197  """
2198 
2199  supported_locales = {
2200  "ar-SA",
2201  "de-DE",
2202  "en-AU",
2203  "en-CA",
2204  "en-GB",
2205  "en-IN",
2206  "en-US",
2207  "es-ES",
2208  "es-MX",
2209  "es-US",
2210  "fr-CA",
2211  "fr-FR",
2212  "hi-IN",
2213  "it-IT",
2214  "ja-JP",
2215  "pt-BR",
2216  }
2217 
2218  def name(self) -> str:
2219  """Return the Alexa API name of this interface."""
2220  return "Alexa.PlaybackStateReporter"
2221 
2222  def properties_supported(self) -> list[dict[str, str]]:
2223  """Return what properties this entity supports."""
2224  return [{"name": "playbackState"}]
2225 
2227  """Return True if properties asynchronously reported."""
2228  return True
2229 
2230  def properties_retrievable(self) -> bool:
2231  """Return True if properties can be retrieved."""
2232  return True
2233 
2234  def get_property(self, name: str) -> Any:
2235  """Read and return a property."""
2236  if name != "playbackState":
2237  raise UnsupportedProperty(name)
2238 
2239  playback_state = self.entityentity.state
2240  if playback_state == STATE_PLAYING:
2241  return {"state": "PLAYING"}
2242  if playback_state == STATE_PAUSED:
2243  return {"state": "PAUSED"}
2244 
2245  return {"state": "STOPPED"}
2246 
2247 
2249  """Implements Alexa.SeekController.
2250 
2251  https://developer.amazon.com/docs/device-apis/alexa-seekcontroller.html
2252  """
2253 
2254  supported_locales = {
2255  "ar-SA",
2256  "de-DE",
2257  "en-AU",
2258  "en-CA",
2259  "en-GB",
2260  "en-IN",
2261  "en-US",
2262  "es-ES",
2263  "es-MX",
2264  "es-US",
2265  "fr-CA",
2266  "fr-FR",
2267  "hi-IN",
2268  "it-IT",
2269  "ja-JP",
2270  "pt-BR",
2271  }
2272 
2273  def name(self) -> str:
2274  """Return the Alexa API name of this interface."""
2275  return "Alexa.SeekController"
2276 
2277 
2279  """Implements Alexa.EventDetectionSensor.
2280 
2281  https://developer.amazon.com/docs/device-apis/alexa-eventdetectionsensor.html
2282  """
2283 
2284  supported_locales = {"en-US"}
2285 
2286  def __init__(self, hass: HomeAssistant, entity: State) -> None:
2287  """Initialize the entity."""
2288  super().__init__(entity)
2289  self.hasshass = hass
2290 
2291  def name(self) -> str:
2292  """Return the Alexa API name of this interface."""
2293  return "Alexa.EventDetectionSensor"
2294 
2295  def properties_supported(self) -> list[dict[str, str]]:
2296  """Return what properties this entity supports."""
2297  return [{"name": "humanPresenceDetectionState"}]
2298 
2300  """Return True if properties asynchronously reported."""
2301  return True
2302 
2303  def get_property(self, name: str) -> Any:
2304  """Read and return a property."""
2305  if name != "humanPresenceDetectionState":
2306  raise UnsupportedProperty(name)
2307 
2308  human_presence = "NOT_DETECTED"
2309  state = self.entityentity.state
2310 
2311  # Return None for unavailable and unknown states.
2312  # Allows the Alexa.EndpointHealth Interface to handle the unavailable
2313  # state in a stateReport.
2314  if state in (STATE_UNAVAILABLE, STATE_UNKNOWN, None):
2315  return None
2316 
2317  if self.entityentity.domain == image_processing.DOMAIN:
2318  if int(state):
2319  human_presence = "DETECTED"
2320  elif state == STATE_ON or self.entityentity.domain in [
2321  input_button.DOMAIN,
2322  button.DOMAIN,
2323  ]:
2324  human_presence = "DETECTED"
2325 
2326  return {"value": human_presence}
2327 
2328  def configuration(self) -> dict[str, Any] | None:
2329  """Return supported detection types."""
2330  return {
2331  "detectionMethods": ["AUDIO", "VIDEO"],
2332  "detectionModes": {
2333  "humanPresence": {
2334  "featureAvailability": "ENABLED",
2335  "supportsNotDetected": self.entityentity.domain
2336  not in [input_button.DOMAIN, button.DOMAIN],
2337  }
2338  },
2339  }
2340 
2341 
2343  """Implements Alexa.EqualizerController.
2344 
2345  https://developer.amazon.com/en-US/docs/alexa/device-apis/alexa-equalizercontroller.html
2346  """
2347 
2348  supported_locales = {
2349  "de-DE",
2350  "en-AU",
2351  "en-CA",
2352  "en-GB",
2353  "en-IN",
2354  "en-US",
2355  "es-ES",
2356  "es-MX",
2357  "es-US",
2358  "fr-CA",
2359  "fr-FR",
2360  "hi-IN",
2361  "it-IT",
2362  "ja-JP",
2363  "pt-BR",
2364  }
2365 
2366  VALID_SOUND_MODES = {
2367  "MOVIE",
2368  "MUSIC",
2369  "NIGHT",
2370  "SPORT",
2371  "TV",
2372  }
2373 
2374  def name(self) -> str:
2375  """Return the Alexa API name of this interface."""
2376  return "Alexa.EqualizerController"
2377 
2378  def properties_supported(self) -> list[dict[str, str]]:
2379  """Return what properties this entity supports.
2380 
2381  Either bands, mode or both can be specified. Only mode is supported
2382  at this time.
2383  """
2384  return [{"name": "mode"}]
2385 
2386  def properties_retrievable(self) -> bool:
2387  """Return True if properties can be retrieved."""
2388  return True
2389 
2390  def get_property(self, name: str) -> Any:
2391  """Read and return a property."""
2392  if name != "mode":
2393  raise UnsupportedProperty(name)
2394 
2395  sound_mode = self.entityentity.attributes.get(media_player.ATTR_SOUND_MODE)
2396  if sound_mode and sound_mode.upper() in self.VALID_SOUND_MODESVALID_SOUND_MODES:
2397  return sound_mode.upper()
2398 
2399  return None
2400 
2401  def configurations(self) -> dict[str, Any] | None:
2402  """Return the sound modes supported in the configurations object."""
2403  configurations = None
2404  supported_sound_modes = self.get_valid_inputsget_valid_inputs(
2405  self.entityentity.attributes.get(media_player.ATTR_SOUND_MODE_LIST) or []
2406  )
2407  if supported_sound_modes:
2408  configurations = {"modes": {"supported": supported_sound_modes}}
2409 
2410  return configurations
2411 
2412  @classmethod
2413  def get_valid_inputs(cls, sound_mode_list: list[str]) -> list[dict[str, str]]:
2414  """Return list of supported inputs."""
2415  input_list: list[dict[str, str]] = []
2416  for sound_mode in sound_mode_list:
2417  sound_mode = sound_mode.upper()
2418 
2419  if sound_mode in cls.VALID_SOUND_MODESVALID_SOUND_MODES:
2420  input_list.append({"name": sound_mode})
2421 
2422  return input_list
2423 
2424 
2426  """Implements Alexa.TimeHoldController.
2427 
2428  https://developer.amazon.com/docs/device-apis/alexa-timeholdcontroller.html
2429  """
2430 
2431  supported_locales = {"en-US"}
2432 
2433  def __init__(self, entity: State, allow_remote_resume: bool = False) -> None:
2434  """Initialize the entity."""
2435  super().__init__(entity)
2436  self._allow_remote_resume_allow_remote_resume = allow_remote_resume
2437 
2438  def name(self) -> str:
2439  """Return the Alexa API name of this interface."""
2440  return "Alexa.TimeHoldController"
2441 
2442  def configuration(self) -> dict[str, Any] | None:
2443  """Return configuration object.
2444 
2445  Set allowRemoteResume to True if Alexa can restart the operation on the device.
2446  When false, Alexa does not send the Resume directive.
2447  """
2448  return {"allowRemoteResume": self._allow_remote_resume_allow_remote_resume}
2449 
2450 
2452  """Implements Alexa.CameraStreamController.
2453 
2454  https://developer.amazon.com/docs/device-apis/alexa-camerastreamcontroller.html
2455  """
2456 
2457  supported_locales = {
2458  "ar-SA",
2459  "de-DE",
2460  "en-AU",
2461  "en-CA",
2462  "en-GB",
2463  "en-IN",
2464  "en-US",
2465  "es-ES",
2466  "es-MX",
2467  "es-US",
2468  "fr-CA",
2469  "fr-FR",
2470  "hi-IN",
2471  "it-IT",
2472  "ja-JP",
2473  "pt-BR",
2474  }
2475 
2476  def name(self) -> str:
2477  """Return the Alexa API name of this interface."""
2478  return "Alexa.CameraStreamController"
2479 
2480  def camera_stream_configurations(self) -> list[dict[str, Any]] | None:
2481  """Return cameraStreamConfigurations object."""
2482  return [
2483  {
2484  "protocols": ["HLS"],
2485  "resolutions": [{"width": 1280, "height": 720}],
2486  "authorizationTypes": ["NONE"],
2487  "videoCodecs": ["H264"],
2488  "audioCodecs": ["AAC"],
2489  }
2490  ]
dict[str, list[dict[str, Any]]] capability_resources(self)
None __init__(self, State entity, str|None instance=None, bool|None non_controllable_properties=None)
list[dict[str, Any]]|None camera_stream_configurations(self)
None __init__(self, HomeAssistant hass, State entity)
None __init__(self, HomeAssistant hass, State entity)
list[dict[str, str]] get_valid_inputs(cls, list[str] sound_mode_list)
list[dict[str, str]] get_valid_inputs(list[Any] source_list)
dict[str, list[dict[str, Any]]] capability_resources(self)
None __init__(self, State entity, str instance, bool non_controllable=False)
None __init__(self, HomeAssistant hass, State entity)
dict[str, list[dict[str, Any]]] capability_resources(self)
None __init__(self, State entity, str|None instance, bool non_controllable=False)
None __init__(self, State entity, bool supports_deactivation)
None __init__(self, HomeAssistant hass, State entity)
None __init__(self, State entity, bool allow_remote_resume=False)
None __init__(self, State entity, str instance, bool non_controllable=False)
str get_resource_by_unit_of_measurement(State entity)
Definition: capabilities.py:98