diff --git a/homeassistant/components/alexa/capabilities.py b/homeassistant/components/alexa/capabilities.py index b8bd3841a78..fca63adab0e 100644 --- a/homeassistant/components/alexa/capabilities.py +++ b/homeassistant/components/alexa/capabilities.py @@ -614,3 +614,38 @@ class AlexaThermostatController(AlexaCapibility): return None return {"value": temp, "scale": API_TEMP_UNITS[unit]} + + +class AlexaPowerLevelController(AlexaCapibility): + """Implements Alexa.PowerLevelController. + + https://developer.amazon.com/docs/device-apis/alexa-powerlevelcontroller.html + """ + + def name(self): + """Return the Alexa API name of this interface.""" + return "Alexa.PowerLevelController" + + def properties_supported(self): + """Return what properties this entity supports.""" + return [{"name": "powerLevel"}] + + def properties_proactively_reported(self): + """Return True if properties asynchronously reported.""" + return True + + def properties_retrievable(self): + """Return True if properties can be retrieved.""" + return True + + def get_property(self, name): + """Read and return a property.""" + if name != "powerLevel": + raise UnsupportedProperty(name) + + if self.entity.domain == fan.DOMAIN: + speed = self.entity.attributes.get(fan.ATTR_SPEED) + + return PERCENTAGE_FAN_MAP.get(speed, None) + + return None diff --git a/homeassistant/components/alexa/const.py b/homeassistant/components/alexa/const.py index 83c7da41c16..cd0cb85a0a5 100644 --- a/homeassistant/components/alexa/const.py +++ b/homeassistant/components/alexa/const.py @@ -62,7 +62,12 @@ API_THERMOSTAT_MODES = OrderedDict( ) API_THERMOSTAT_PRESETS = {climate.PRESET_ECO: "ECO"} -PERCENTAGE_FAN_MAP = {fan.SPEED_LOW: 33, fan.SPEED_MEDIUM: 66, fan.SPEED_HIGH: 100} +PERCENTAGE_FAN_MAP = { + fan.SPEED_OFF: 0, + fan.SPEED_LOW: 33, + fan.SPEED_MEDIUM: 66, + fan.SPEED_HIGH: 100, +} class Cause: diff --git a/homeassistant/components/alexa/entities.py b/homeassistant/components/alexa/entities.py index 55b5878f667..63231f71447 100644 --- a/homeassistant/components/alexa/entities.py +++ b/homeassistant/components/alexa/entities.py @@ -43,6 +43,7 @@ from .capabilities import ( AlexaPercentageController, AlexaPlaybackController, AlexaPowerController, + AlexaPowerLevelController, AlexaSceneController, AlexaSpeaker, AlexaStepSpeaker, @@ -344,6 +345,7 @@ class FanCapabilities(AlexaEntity): supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0) if supported & fan.SUPPORT_SET_SPEED: yield AlexaPercentageController(self.entity) + yield AlexaPowerLevelController(self.entity) yield AlexaEndpointHealth(self.hass, self.entity) diff --git a/homeassistant/components/alexa/handlers.py b/homeassistant/components/alexa/handlers.py index c72101460c4..3cb61675f92 100644 --- a/homeassistant/components/alexa/handlers.py +++ b/homeassistant/components/alexa/handlers.py @@ -779,3 +779,73 @@ async def async_api_set_thermostat_mode(hass, config, directive, context): async def async_api_reportstate(hass, config, directive, context): """Process a ReportState request.""" return directive.response(name="StateReport") + + +@HANDLERS.register(("Alexa.PowerLevelController", "SetPowerLevel")) +async def async_api_set_power_level(hass, config, directive, context): + """Process a SetPowerLevel request.""" + entity = directive.entity + percentage = int(directive.payload["powerLevel"]) + service = None + data = {ATTR_ENTITY_ID: entity.entity_id} + + if entity.domain == fan.DOMAIN: + service = fan.SERVICE_SET_SPEED + speed = "off" + + if percentage <= 33: + speed = "low" + elif percentage <= 66: + speed = "medium" + else: + speed = "high" + + data[fan.ATTR_SPEED] = speed + + await hass.services.async_call( + entity.domain, service, data, blocking=False, context=context + ) + + return directive.response() + + +@HANDLERS.register(("Alexa.PowerLevelController", "AdjustPowerLevel")) +async def async_api_adjust_power_level(hass, config, directive, context): + """Process an AdjustPowerLevel request.""" + entity = directive.entity + percentage_delta = int(directive.payload["powerLevelDelta"]) + service = None + data = {ATTR_ENTITY_ID: entity.entity_id} + current = 0 + + if entity.domain == fan.DOMAIN: + service = fan.SERVICE_SET_SPEED + speed = entity.attributes.get(fan.ATTR_SPEED) + + if speed == "off": + current = 0 + elif speed == "low": + current = 33 + elif speed == "medium": + current = 66 + else: + current = 100 + + # set percentage + percentage = max(0, percentage_delta + current) + speed = "off" + + if percentage <= 33: + speed = "low" + elif percentage <= 66: + speed = "medium" + else: + speed = "high" + + data[fan.ATTR_SPEED] = speed + + await hass.services.async_call( + entity.domain, service, data, blocking=False, context=context + ) + + return directive.response() diff --git a/tests/components/alexa/test_smart_home.py b/tests/components/alexa/test_smart_home.py index e5e5b8ab7ae..78ce2963eaf 100644 --- a/tests/components/alexa/test_smart_home.py +++ b/tests/components/alexa/test_smart_home.py @@ -340,6 +340,7 @@ async def test_variable_fan(hass): appliance, "Alexa.PercentageController", "Alexa.PowerController", + "Alexa.PowerLevelController", "Alexa.EndpointHealth", ) @@ -364,6 +365,27 @@ async def test_variable_fan(hass): "speed", ) + call, _ = await assert_request_calls_service( + "Alexa.PowerLevelController", + "SetPowerLevel", + "fan#test_2", + "fan.set_speed", + hass, + payload={"powerLevel": "50"}, + ) + assert call.data["speed"] == "medium" + + await assert_percentage_changes( + hass, + [("high", "-5"), ("high", "5"), ("low", "-80")], + "Alexa.PowerLevelController", + "AdjustPowerLevel", + "fan#test_2", + "powerLevelDelta", + "fan.set_speed", + "speed", + ) + async def test_lock(hass): """Test lock discovery."""