diff --git a/homeassistant/components/alexa/capabilities.py b/homeassistant/components/alexa/capabilities.py index efa2ee3a48a..ca497ade9ad 100644 --- a/homeassistant/components/alexa/capabilities.py +++ b/homeassistant/components/alexa/capabilities.py @@ -135,16 +135,16 @@ class AlexaCapability: def configuration(self): """Return the configuration object. - Applicable to the ThermostatController, SecurityControlPanel, ModeController, RangeController, - and EventDetectionSensor. + Applicable to the ThermostatController, SecurityControlPanel, ModeController, + RangeController, and EventDetectionSensor. """ return [] def configurations(self): """Return the configurations object. - The plural configurations object is different that the singular configuration object. - Applicable to EqualizerController interface. + The plural configurations object is different that the singular configuration + object. Applicable to EqualizerController interface. """ return [] @@ -196,7 +196,8 @@ class AlexaCapability: if configuration := self.configuration(): result["configuration"] = configuration - # The plural configurations object is different than the singular configuration object above. + # The plural configurations object is different than the singular + # configuration object above. if configurations := self.configurations(): result["configurations"] = configurations @@ -757,7 +758,8 @@ class AlexaPlaybackController(AlexaCapability): def supported_operations(self): """Return the supportedOperations object. - Supported Operations: FastForward, Next, Pause, Play, Previous, Rewind, StartOver, Stop + Supported Operations: FastForward, Next, Pause, Play, Previous, Rewind, + StartOver, Stop """ supported_features = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0) @@ -1117,7 +1119,9 @@ class AlexaThermostatController(AlexaCapability): def configuration(self): """Return configuration object. - Translates climate HVAC_MODES and PRESETS to supported Alexa ThermostatMode Values. + Translates climate HVAC_MODES and PRESETS to supported Alexa + ThermostatMode Values. + ThermostatMode Value must be AUTO, COOL, HEAT, ECO, OFF, or CUSTOM. """ supported_modes = [] @@ -1133,7 +1137,8 @@ class AlexaThermostatController(AlexaCapability): if thermostat_mode: supported_modes.append(thermostat_mode) - # Return False for supportsScheduling until supported with event listener in handler. + # Return False for supportsScheduling until supported with event + # listener in handler. configuration = {"supportsScheduling": False} if supported_modes: @@ -1270,12 +1275,15 @@ class AlexaSecurityPanelController(AlexaCapability): class AlexaModeController(AlexaCapability): """Implements Alexa.ModeController. - The instance property must be unique across ModeController, RangeController, ToggleController within the same device. - The instance property should be a concatenated string of device domain period and single word. - e.g. fan.speed & fan.direction. + The instance property must be unique across ModeController, RangeController, + ToggleController within the same device. - The instance property must not contain words from other instance property strings within the same device. - e.g. Instance property cover.position & cover.tilt_position will cause the Alexa.Discovery directive to fail. + The instance property should be a concatenated string of device domain period + and single word. e.g. fan.speed & fan.direction. + + The instance property must not contain words from other instance property + strings within the same device. e.g. Instance property cover.position & + cover.tilt_position will cause the Alexa.Discovery directive to fail. An instance property string value may be reused for different devices. @@ -1408,8 +1416,8 @@ class AlexaModeController(AlexaCapability): modes = self.entity.attributes.get(humidifier.ATTR_AVAILABLE_MODES, []) for mode in modes: self._resource.add_mode(f"{humidifier.ATTR_MODE}.{mode}", [mode]) - # Humidifiers or Fans with a single mode completely break Alexa discovery, add a - # fake preset (see issue #53832). + # Humidifiers or Fans with a single mode completely break Alexa discovery, + # add a fake preset (see issue #53832). if len(modes) == 1: self._resource.add_mode( f"{humidifier.ATTR_MODE}.{PRESET_MODE_NA}", [PRESET_MODE_NA] @@ -1479,12 +1487,15 @@ class AlexaModeController(AlexaCapability): class AlexaRangeController(AlexaCapability): """Implements Alexa.RangeController. - The instance property must be unique across ModeController, RangeController, ToggleController within the same device. - The instance property should be a concatenated string of device domain period and single word. - e.g. fan.speed & fan.direction. + The instance property must be unique across ModeController, RangeController, + ToggleController within the same device. - The instance property must not contain words from other instance property strings within the same device. - e.g. Instance property cover.position & cover.tilt_position will cause the Alexa.Discovery directive to fail. + The instance property should be a concatenated string of device domain period + and single word. e.g. fan.speed & fan.direction. + + The instance property must not contain words from other instance property + strings within the same device. e.g. Instance property cover.position & + cover.tilt_position will cause the Alexa.Discovery directive to fail. An instance property string value may be reused for different devices. @@ -1538,7 +1549,8 @@ class AlexaRangeController(AlexaCapability): raise UnsupportedProperty(name) # Return None for unavailable and unknown states. - # Allows the Alexa.EndpointHealth Interface to handle the unavailable state in a stateReport. + # Allows the Alexa.EndpointHealth Interface to handle the unavailable + # state in a stateReport. if self.entity.state in (STATE_UNAVAILABLE, STATE_UNKNOWN, None): return None @@ -1760,12 +1772,15 @@ class AlexaRangeController(AlexaCapability): class AlexaToggleController(AlexaCapability): """Implements Alexa.ToggleController. - The instance property must be unique across ModeController, RangeController, ToggleController within the same device. - The instance property should be a concatenated string of device domain period and single word. - e.g. fan.speed & fan.direction. + The instance property must be unique across ModeController, RangeController, + ToggleController within the same device. - The instance property must not contain words from other instance property strings within the same device. - e.g. Instance property cover.position & cover.tilt_position will cause the Alexa.Discovery directive to fail. + The instance property should be a concatenated string of device domain period + and single word. e.g. fan.speed & fan.direction. + + The instance property must not contain words from other instance property + strings within the same device. e.g. Instance property cover.position + & cover.tilt_position will cause the Alexa.Discovery directive to fail. An instance property string value may be reused for different devices. @@ -2021,7 +2036,8 @@ class AlexaEventDetectionSensor(AlexaCapability): state = self.entity.state # Return None for unavailable and unknown states. - # Allows the Alexa.EndpointHealth Interface to handle the unavailable state in a stateReport. + # Allows the Alexa.EndpointHealth Interface to handle the unavailable + # state in a stateReport. if state in (STATE_UNAVAILABLE, STATE_UNKNOWN, None): return None @@ -2089,7 +2105,8 @@ class AlexaEqualizerController(AlexaCapability): def properties_supported(self): """Return what properties this entity supports. - Either bands, mode or both can be specified. Only mode is supported at this time. + Either bands, mode or both can be specified. Only mode is supported + at this time. """ return [{"name": "mode"}] diff --git a/homeassistant/components/alexa/entities.py b/homeassistant/components/alexa/entities.py index 77d35a1582c..40aec230010 100644 --- a/homeassistant/components/alexa/entities.py +++ b/homeassistant/components/alexa/entities.py @@ -103,7 +103,8 @@ class DisplayCategory: # Indicates a device that cools the air in interior spaces. AIR_CONDITIONER = "AIR_CONDITIONER" - # Indicates a device that emits pleasant odors and masks unpleasant odors in interior spaces. + # Indicates a device that emits pleasant odors and masks unpleasant + # odors in interior spaces. AIR_FRESHENER = "AIR_FRESHENER" # Indicates a device that improves the quality of air in interior spaces. @@ -143,7 +144,8 @@ class DisplayCategory: GAME_CONSOLE = "GAME_CONSOLE" # Indicates a garage door. - # Garage doors must implement the ModeController interface to open and close the door. + # Garage doors must implement the ModeController interface to + # open and close the door. GARAGE_DOOR = "GARAGE_DOOR" # Indicates a wearable device that transmits audio directly into the ear. @@ -206,8 +208,8 @@ class DisplayCategory: # Indicates a security system. SECURITY_SYSTEM = "SECURITY_SYSTEM" - # Indicates an electric cooking device that sits on a countertop, cooks at low temperatures, - # and is often shaped like a cooking pot. + # Indicates an electric cooking device that sits on a countertop, + # cooks at low temperatures, and is often shaped like a cooking pot. SLOW_COOKER = "SLOW_COOKER" # Indicates an endpoint that locks. @@ -243,7 +245,8 @@ class DisplayCategory: # Indicates a vacuum cleaner. VACUUM_CLEANER = "VACUUM_CLEANER" - # Indicates a network-connected wearable device, such as an Apple Watch, Fitbit, or Samsung Gear. + # Indicates a network-connected wearable device, such as an Apple Watch, + # Fitbit, or Samsung Gear. WEARABLE = "WEARABLE" @@ -574,9 +577,10 @@ class FanCapabilities(AlexaEntity): force_range_controller = False # AlexaRangeController controls the Fan Speed Percentage. - # For fans which only support on/off, no controller is added. This makes the - # fan impossible to turn on or off through Alexa, most likely due to a bug in Alexa. - # As a workaround, we add a range controller which can only be set to 0% or 100%. + # For fans which only support on/off, no controller is added. This makes + # the fan impossible to turn on or off through Alexa, most likely due + # to a bug in Alexa. As a workaround, we add a range controller which + # can only be set to 0% or 100%. if force_range_controller or supported & fan.FanEntityFeature.SET_SPEED: yield AlexaRangeController( self.entity, instance=f"{fan.DOMAIN}.{fan.ATTR_PERCENTAGE}" diff --git a/homeassistant/components/alexa/handlers.py b/homeassistant/components/alexa/handlers.py index 24ab3ec10e3..97c7f4297ff 100644 --- a/homeassistant/components/alexa/handlers.py +++ b/homeassistant/components/alexa/handlers.py @@ -613,9 +613,10 @@ async def async_api_adjust_volume_step( """Process an adjust volume step request.""" # media_player volume up/down service does not support specifying steps # each component handles it differently e.g. via config. - # This workaround will simply call the volume up/Volume down the amount of steps asked for - # When no steps are called in the request, Alexa sends a default of 10 steps which for most - # purposes is too high. The default is set 1 in this case. + # This workaround will simply call the volume up/Volume down the amount of + # steps asked for. When no steps are called in the request, Alexa sends + # a default of 10 steps which for most purposes is too high. The default + # is set 1 in this case. entity = directive.entity volume_int = int(directive.payload["volumeSteps"]) is_default = bool(directive.payload["volumeStepsDefault"]) @@ -1020,8 +1021,9 @@ async def async_api_disarm( data = {ATTR_ENTITY_ID: entity.entity_id} response = directive.response() - # Per Alexa Documentation: If you receive a Disarm directive, and the system is already disarmed, - # respond with a success response, not an error response. + # Per Alexa Documentation: If you receive a Disarm directive, and the + # system is already disarmed, respond with a success response, + # not an error response. if entity.state == STATE_ALARM_DISARMED: return response @@ -1136,7 +1138,8 @@ async def async_api_adjust_mode( Only supportedModes with ordered=True support the adjustMode directive. """ - # Currently no supportedModes are configured with ordered=True to support this request. + # Currently no supportedModes are configured with ordered=True + # to support this request. raise AlexaInvalidDirectiveError(DIRECTIVE_NOT_SUPPORTED) @@ -1483,7 +1486,9 @@ async def async_api_changechannel( data = { ATTR_ENTITY_ID: entity.entity_id, media_player.const.ATTR_MEDIA_CONTENT_ID: channel, - media_player.const.ATTR_MEDIA_CONTENT_TYPE: media_player.const.MEDIA_TYPE_CHANNEL, + media_player.const.ATTR_MEDIA_CONTENT_TYPE: ( + media_player.const.MEDIA_TYPE_CHANNEL + ), } await hass.services.async_call( diff --git a/homeassistant/components/alexa/resources.py b/homeassistant/components/alexa/resources.py index fb207f17ff4..e171cf0ebdc 100644 --- a/homeassistant/components/alexa/resources.py +++ b/homeassistant/components/alexa/resources.py @@ -6,12 +6,15 @@ class AlexaGlobalCatalog: https://developer.amazon.com/docs/device-apis/resources-and-assets.html#global-alexa-catalog - You can use the global Alexa catalog for pre-defined names of devices, settings, values, and units. - This catalog is localized into all the languages that Alexa supports. + You can use the global Alexa catalog for pre-defined names of devices, settings, + values, and units. + This catalog is localized into all the languages that Alexa supports. You can reference the following catalog of pre-defined friendly names. - Each item in the following list is an asset identifier followed by its supported friendly names. - The first friendly name for each identifier is the one displayed in the Alexa mobile app. + + Each item in the following list is an asset identifier followed by its + supported friendly names. The first friendly name for each identifier is + the one displayed in the Alexa mobile app. """ # Air Purifier, Air Cleaner,Clean Air Machine @@ -23,7 +26,8 @@ class AlexaGlobalCatalog: # Router, Internet Router, Network Router, Wifi Router, Net Router DEVICE_NAME_ROUTER = "Alexa.DeviceName.Router" - # Shade, Blind, Curtain, Roller, Shutter, Drape, Awning, Window shade, Interior blind + # Shade, Blind, Curtain, Roller, Shutter, Drape, Awning, + # Window shade, Interior blind DEVICE_NAME_SHADE = "Alexa.DeviceName.Shade" # Shower @@ -190,10 +194,13 @@ class AlexaGlobalCatalog: class AlexaCapabilityResource: - """Base class for Alexa capabilityResources, modeResources, and presetResources objects. + """Base class for Alexa capabilityResources, modeResources, and presetResources. + + Resources objects labels must be unique across all modeResources and + presetResources within the same device. To provide support for all + supported locales, include one label from the AlexaGlobalCatalog in the + labels array. - Resources objects labels must be unique across all modeResources and presetResources within the same device. - To provide support for all supported locales, include one label from the AlexaGlobalCatalog in the labels array. You cannot use any words from the following list as friendly names: https://developer.amazon.com/docs/alexa/device-apis/resources-and-assets.html#names-you-cannot-use @@ -211,11 +218,17 @@ class AlexaCapabilityResource: return self.serialize_labels(self._resource_labels) def serialize_configuration(self): - """Return ModeResources, PresetResources friendlyNames serialized for an API response.""" + """Return serialized configuration for an API response. + + Return ModeResources, PresetResources friendlyNames serialized. + """ return [] def serialize_labels(self, resources): - """Return resource label objects for friendlyNames serialized for an API response.""" + """Return serialized labels for an API response. + + Returns resource label objects for friendlyNames serialized. + """ labels = [] for label in resources: if label in AlexaGlobalCatalog.__dict__.values(): @@ -245,7 +258,10 @@ class AlexaModeResource(AlexaCapabilityResource): self._supported_modes.append({"value": value, "labels": labels}) def serialize_configuration(self): - """Return configuration for ModeResources friendlyNames serialized for an API response.""" + """Return serialized configuration for an API response. + + Returns configuration for ModeResources friendlyNames serialized. + """ mode_resources = [] for mode in self._supported_modes: result = { @@ -260,7 +276,8 @@ class AlexaModeResource(AlexaCapabilityResource): class AlexaPresetResource(AlexaCapabilityResource): """Implements Alexa PresetResources. - Use presetResources with RangeController to provide a set of friendlyNames for each RangeController preset. + Use presetResources with RangeController to provide a set of + friendlyNamesfor each RangeController preset. https://developer.amazon.com/docs/device-apis/resources-and-assets.html#presetresources """ @@ -281,7 +298,10 @@ class AlexaPresetResource(AlexaCapabilityResource): self._presets.append({"value": value, "labels": labels}) def serialize_configuration(self): - """Return configuration for PresetResources friendlyNames serialized for an API response.""" + """Return serialized configuration for an API response. + + Returns configuration for PresetResources friendlyNames serialized. + """ configuration = { "supportedRange": { "minimumValue": self._minimum_value, @@ -309,18 +329,23 @@ class AlexaPresetResource(AlexaCapabilityResource): class AlexaSemantics: """Class for Alexa Semantics Object. - You can optionally enable additional utterances by using semantics. When you use semantics, - you manually map the phrases "open", "close", "raise", and "lower" to directives. + You can optionally enable additional utterances by using semantics. When + you use semantics, you manually map the phrases "open", "close", "raise", + and "lower" to directives. - Semantics is supported for the following interfaces only: ModeController, RangeController, and ToggleController. + Semantics is supported for the following interfaces only: ModeController, + RangeController, and ToggleController. - Semantics stateMappings are only supported for one interface of the same type on the same device. If a device has - multiple RangeControllers only one interface may use stateMappings otherwise discovery will fail. + Semantics stateMappings are only supported for one interface of the same + type on the same device. If a device has multiple RangeControllers only + one interface may use stateMappings otherwise discovery will fail. - You can support semantics actionMappings on different controllers for the same device, however each controller must - support different phrases. For example, you can support "raise" on a RangeController, and "open" on a ModeController, - but you can't support "open" on both RangeController and ModeController. Semantics stateMappings are only supported - for one interface on the same device. + You can support semantics actionMappings on different controllers for the + same device, however each controller must support different phrases. + For example, you can support "raise" on a RangeController, and "open" + on a ModeController, but you can't support "open" on both RangeController + and ModeController. Semantics stateMappings are only supported for one + interface on the same device. https://developer.amazon.com/docs/device-apis/alexa-discovery.html#semantics-object """ diff --git a/tests/components/alexa/test_capabilities.py b/tests/components/alexa/test_capabilities.py index 71d4d3a5585..6b0ed360517 100644 --- a/tests/components/alexa/test_capabilities.py +++ b/tests/components/alexa/test_capabilities.py @@ -412,7 +412,7 @@ async def test_report_fan_speed_state(hass): async def test_report_humidifier_humidity_state(hass): - """Test PercentageController, PowerLevelController reports humidifier humidity correctly.""" + """Test PercentageController, PowerLevelController humidifier humidity reporting.""" hass.states.async_set( "humidifier.dry", "on", @@ -934,7 +934,10 @@ async def test_report_image_processing(hass): @pytest.mark.parametrize("domain", ["button", "input_button"]) async def test_report_button_pressed(hass, domain): - """Test button presses report human presence detection events to trigger routines.""" + """Test button presses report human presence detection events. + + For use to trigger routines. + """ hass.states.async_set( f"{domain}.test_button", "now", {"friendly_name": "Test button"} ) diff --git a/tests/components/alexa/test_entities.py b/tests/components/alexa/test_entities.py index fb364dbf14e..cd175338f6a 100644 --- a/tests/components/alexa/test_entities.py +++ b/tests/components/alexa/test_entities.py @@ -114,6 +114,6 @@ async def test_serialize_discovery_recovers(hass, caplog): assert "Alexa.PowerController" not in interfaces assert ( - f"Error serializing Alexa.PowerController discovery for {hass.states.get('switch.bla')}" - in caplog.text - ) + f"Error serializing Alexa.PowerController discovery" + f" for {hass.states.get('switch.bla')}" + ) in caplog.text diff --git a/tests/components/alexa/test_intent.py b/tests/components/alexa/test_intent.py index 54708e9d0f0..b7ff6ce6b40 100644 --- a/tests/components/alexa/test_intent.py +++ b/tests/components/alexa/test_intent.py @@ -193,7 +193,9 @@ async def test_intent_launch_request_not_configured(alexa_client): "new": True, "sessionId": SESSION_ID, "application": { - "applicationId": "amzn1.echo-sdk-ams.app.000000-d0ed-0000-ad00-000000d00000" + "applicationId": ( + "amzn1.echo-sdk-ams.app.000000-d0ed-0000-ad00-000000d00000" + ), }, "attributes": {}, "user": {"userId": "amzn1.account.AM3B00000000000000000000000"}, diff --git a/tests/components/alexa/test_smart_home.py b/tests/components/alexa/test_smart_home.py index 5ccac23a2fd..54f5ca38f69 100644 --- a/tests/components/alexa/test_smart_home.py +++ b/tests/components/alexa/test_smart_home.py @@ -410,7 +410,8 @@ async def test_fan(hass): assert appliance["endpointId"] == "fan#test_1" assert appliance["displayCategories"][0] == "FAN" assert appliance["friendlyName"] == "Test fan 1" - # Alexa.RangeController is added to make a fan controllable when no other controllers are available + # Alexa.RangeController is added to make a fan controllable when + # no other controllers are available. capabilities = assert_endpoint_capabilities( appliance, "Alexa.RangeController", @@ -466,7 +467,8 @@ async def test_fan2(hass): assert appliance["endpointId"] == "fan#test_2" assert appliance["displayCategories"][0] == "FAN" assert appliance["friendlyName"] == "Test fan 2" - # Alexa.RangeController is added to make a fan controllable when no other controllers are available + # Alexa.RangeController is added to make a fan controllable + # when no other controllers are available capabilities = assert_endpoint_capabilities( appliance, "Alexa.RangeController", @@ -597,7 +599,8 @@ async def test_variable_fan_no_current_speed(hass, caplog): assert appliance["endpointId"] == "fan#test_3" assert appliance["displayCategories"][0] == "FAN" assert appliance["friendlyName"] == "Test fan 3" - # Alexa.RangeController is added to make a van controllable when no other controllers are available + # Alexa.RangeController is added to make a van controllable + # when no other controllers are available capabilities = assert_endpoint_capabilities( appliance, "Alexa.RangeController", @@ -625,9 +628,9 @@ async def test_variable_fan_no_current_speed(hass, caplog): "fan.percentage", ) assert ( - "Request Alexa.RangeController/AdjustRangeValue error INVALID_VALUE: Unable to determine fan.test_3 current fan speed" - in caplog.text - ) + "Request Alexa.RangeController/AdjustRangeValue error " + "INVALID_VALUE: Unable to determine fan.test_3 current fan speed" + ) in caplog.text caplog.clear() diff --git a/tests/components/alexa/test_state_report.py b/tests/components/alexa/test_state_report.py index ed70afc02d6..cd8e389d172 100644 --- a/tests/components/alexa/test_state_report.py +++ b/tests/components/alexa/test_state_report.py @@ -527,8 +527,9 @@ async def test_doorbell_event_fail(hass, aioclient_mock, caplog): # Check we log the entity id of the failing entity assert ( - "Error when sending DoorbellPress event for binary_sensor.test_doorbell to Alexa: " - "THROTTLING_EXCEPTION: Request could not be processed due to throttling" + "Error when sending DoorbellPress event for binary_sensor.test_doorbell" + " to Alexa: THROTTLING_EXCEPTION: Request could not be processed" + " due to throttling" ) in caplog.text