From fbdf37904a3e613f4d3107a1b176fdfe6c9429bf Mon Sep 17 00:00:00 2001 From: tim Date: Sun, 23 Jun 2024 22:01:44 +0000 Subject: [PATCH] Updated the CapabilityResource to support labels with the corresponding locale. This local is read from the users config. --- .../components/alexa/capabilities.py | 28 +++++++++++++------ homeassistant/components/alexa/entities.py | 25 +++++++++++++---- homeassistant/components/alexa/resources.py | 20 +++++++++---- 3 files changed, 54 insertions(+), 19 deletions(-) diff --git a/homeassistant/components/alexa/capabilities.py b/homeassistant/components/alexa/capabilities.py index d78621678e7..96041849472 100644 --- a/homeassistant/components/alexa/capabilities.py +++ b/homeassistant/components/alexa/capabilities.py @@ -3,7 +3,7 @@ from __future__ import annotations import logging -from typing import Any +from typing import TYPE_CHECKING, Any from typing_extensions import Generator @@ -60,6 +60,9 @@ from homeassistant.core import HomeAssistant, State import homeassistant.util.color as color_util import homeassistant.util.dt as dt_util +if TYPE_CHECKING: + from .config import AbstractConfig + from .const import ( API_TEMP_UNITS, API_THERMOSTAT_MODES, @@ -126,11 +129,13 @@ class AlexaCapability: self, entity: State, instance: str | None = None, + config: AbstractConfig | None = None, non_controllable_properties: bool | None = None, ) -> None: """Initialize an Alexa capability.""" self.entity = entity self.instance = instance + self.config = config self._non_controllable_properties = non_controllable_properties def name(self) -> str: @@ -1390,10 +1395,14 @@ class AlexaModeController(AlexaCapability): } def __init__( - self, entity: State, instance: str, non_controllable: bool = False + self, + entity: State, + instance: str, + config: AbstractConfig, + non_controllable: bool = False, ) -> None: """Initialize the entity.""" - AlexaCapability.__init__(self, entity, instance, non_controllable) + AlexaCapability.__init__(self, entity, instance, config, non_controllable) self._resource = None self._semantics = None @@ -1562,10 +1571,13 @@ class AlexaModeController(AlexaCapability): # Remote Resource if self.instance == f"{remote.DOMAIN}.{remote.ATTR_ACTIVITY}": self._resource = AlexaModeResource([AlexaGlobalCatalog.SETTING_MODE], False) - activities = self.entity.attributes.get(remote.ATTR_ACTIVITY_LIST) or [] + activities: list[str] = ( + self.entity.attributes.get(remote.ATTR_ACTIVITY_LIST) or [] + ) + locale = self.config.locale if self.config else "" for activity in activities: - self._resource.add_mode( - f"{remote.ATTR_ACTIVITY}.{activity}", [activity] + self._resource.add_mode_with_locale( + f"{remote.ATTR_ACTIVITY}.{activity}", {activity: locale or ""} ) # Remotes with a single preset_mode completely break Alexa discovery, add a # fake preset (see issue #53832). @@ -1729,7 +1741,7 @@ class AlexaRangeController(AlexaCapability): self, entity: State, instance: str | None, non_controllable: bool = False ) -> None: """Initialize the entity.""" - AlexaCapability.__init__(self, entity, instance, non_controllable) + AlexaCapability.__init__(self, entity, instance, None, non_controllable) self._resource = None self._semantics = None @@ -2072,7 +2084,7 @@ class AlexaToggleController(AlexaCapability): self, entity: State, instance: str, non_controllable: bool = False ) -> None: """Initialize the entity.""" - AlexaCapability.__init__(self, entity, instance, non_controllable) + AlexaCapability.__init__(self, entity, instance, None, non_controllable) self._resource = None self._semantics = None diff --git a/homeassistant/components/alexa/entities.py b/homeassistant/components/alexa/entities.py index d757884b292..abd7a993d39 100644 --- a/homeassistant/components/alexa/entities.py +++ b/homeassistant/components/alexa/entities.py @@ -504,6 +504,7 @@ class ClimateCapabilities(AlexaEntity): yield AlexaModeController( self.entity, instance=f"{water_heater.DOMAIN}.{water_heater.ATTR_OPERATION_MODE}", + config=self.config, ) yield AlexaEndpointHealth(self.hass, self.entity) yield Alexa(self.entity) @@ -553,7 +554,9 @@ class CoverCapabilities(AlexaEntity): cover.CoverEntityFeature.CLOSE | cover.CoverEntityFeature.OPEN ): yield AlexaModeController( - self.entity, instance=f"{cover.DOMAIN}.{cover.ATTR_POSITION}" + self.entity, + instance=f"{cover.DOMAIN}.{cover.ATTR_POSITION}", + config=self.config, ) if supported & cover.CoverEntityFeature.SET_TILT_POSITION: yield AlexaRangeController(self.entity, instance=f"{cover.DOMAIN}.tilt") @@ -625,12 +628,16 @@ class FanCapabilities(AlexaEntity): force_range_controller = False if supported & fan.FanEntityFeature.PRESET_MODE: yield AlexaModeController( - self.entity, instance=f"{fan.DOMAIN}.{fan.ATTR_PRESET_MODE}" + self.entity, + instance=f"{fan.DOMAIN}.{fan.ATTR_PRESET_MODE}", + config=self.config, ) force_range_controller = False if supported & fan.FanEntityFeature.DIRECTION: yield AlexaModeController( - self.entity, instance=f"{fan.DOMAIN}.{fan.ATTR_DIRECTION}" + self.entity, + instance=f"{fan.DOMAIN}.{fan.ATTR_DIRECTION}", + config=self.config, ) force_range_controller = False @@ -660,7 +667,9 @@ class RemoteCapabilities(AlexaEntity): """Yield the supported interfaces.""" yield AlexaPowerController(self.entity) yield AlexaModeController( - self.entity, instance=f"{remote.DOMAIN}.{remote.ATTR_ACTIVITY}" + self.entity, + instance=f"{remote.DOMAIN}.{remote.ATTR_ACTIVITY}", + config=self.config, ) yield AlexaEndpointHealth(self.hass, self.entity) yield Alexa(self.entity) @@ -680,7 +689,9 @@ class HumidifierCapabilities(AlexaEntity): supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0) if supported & humidifier.HumidifierEntityFeature.MODES: yield AlexaModeController( - self.entity, instance=f"{humidifier.DOMAIN}.{humidifier.ATTR_MODE}" + self.entity, + instance=f"{humidifier.DOMAIN}.{humidifier.ATTR_MODE}", + config=self.config, ) yield AlexaRangeController( self.entity, instance=f"{humidifier.DOMAIN}.{humidifier.ATTR_HUMIDITY}" @@ -1012,7 +1023,9 @@ class ValveCapabilities(AlexaEntity): elif supported & ( valve.ValveEntityFeature.CLOSE | valve.ValveEntityFeature.OPEN ): - yield AlexaModeController(self.entity, instance=f"{valve.DOMAIN}.state") + yield AlexaModeController( + self.entity, instance=f"{valve.DOMAIN}.state", config=self.config + ) if supported & valve.ValveEntityFeature.STOP: yield AlexaToggleController(self.entity, instance=f"{valve.DOMAIN}.stop") yield AlexaEndpointHealth(self.hass, self.entity) diff --git a/homeassistant/components/alexa/resources.py b/homeassistant/components/alexa/resources.py index 7782716798a..1dc18c9c975 100644 --- a/homeassistant/components/alexa/resources.py +++ b/homeassistant/components/alexa/resources.py @@ -211,9 +211,9 @@ class AlexaCapabilityResource: def __init__(self, labels: list[str]) -> None: """Initialize an Alexa resource.""" - self._resource_labels = [] + self._resource_labels: dict[str, str] = {} for label in labels: - self._resource_labels.append(label) + self._resource_labels.update({label: ""}) def serialize_capability_resources(self) -> dict[str, list[dict[str, Any]]]: """Return capabilityResources object serialized for an API response.""" @@ -226,20 +226,22 @@ class AlexaCapabilityResource: """ raise NotImplementedError - def serialize_labels(self, resources: list[str]) -> dict[str, list[dict[str, Any]]]: + def serialize_labels( + self, resources: dict[str, str] + ) -> dict[str, list[dict[str, Any]]]: """Return serialized labels for an API response. Returns resource label objects for friendlyNames serialized. """ labels: list[dict[str, Any]] = [] label_dict: dict[str, Any] - for label in resources: + for label, locale in resources.items(): if label in AlexaGlobalCatalog.__dict__.values(): label_dict = {"@type": "asset", "value": {"assetId": label}} else: label_dict = { "@type": "text", - "value": {"text": label, "locale": "en-US"}, + "value": {"text": label, "locale": locale or "en-US"}, } labels.append(label_dict) @@ -260,6 +262,10 @@ class AlexaModeResource(AlexaCapabilityResource): self._mode_ordered: bool = ordered def add_mode(self, value: str, labels: list[str]) -> None: + """Add mode to the supportedModes object.""" + self.add_mode_with_locale(value, {label: "" for label in labels}) + + def add_mode_with_locale(self, value: str, labels: dict[str, str]) -> None: """Add mode to the supportedModes object.""" self._supported_modes.append({"value": value, "labels": labels}) @@ -307,6 +313,10 @@ class AlexaPresetResource(AlexaCapabilityResource): self._unit_of_measure = unit def add_preset(self, value: float, labels: list[str]) -> None: + """Add preset to configuration presets array.""" + self.add_preset_with_locale(value, {label: "" for label in labels}) + + def add_preset_with_locale(self, value: float, labels: dict[str, str]) -> None: """Add preset to configuration presets array.""" self._presets.append({"value": value, "labels": labels})