diff --git a/homeassistant/components/alexa/capabilities.py b/homeassistant/components/alexa/capabilities.py index 5b675779a22..818b4b794cf 100644 --- a/homeassistant/components/alexa/capabilities.py +++ b/homeassistant/components/alexa/capabilities.py @@ -4,9 +4,11 @@ from __future__ import annotations import logging from homeassistant.components import ( + button, cover, fan, image_processing, + input_button, input_number, light, timer, @@ -1891,7 +1893,10 @@ class AlexaEventDetectionSensor(AlexaCapability): if self.entity.domain == image_processing.DOMAIN: if int(state): human_presence = "DETECTED" - elif state == STATE_ON: + elif state == STATE_ON or self.entity.domain in [ + input_button.DOMAIN, + button.DOMAIN, + ]: human_presence = "DETECTED" return {"value": human_presence} @@ -1903,7 +1908,8 @@ class AlexaEventDetectionSensor(AlexaCapability): "detectionModes": { "humanPresence": { "featureAvailability": "ENABLED", - "supportsNotDetected": True, + "supportsNotDetected": self.entity.domain + not in [input_button.DOMAIN, button.DOMAIN], } }, } diff --git a/homeassistant/components/alexa/entities.py b/homeassistant/components/alexa/entities.py index 9ee4ad3411f..f380f990449 100644 --- a/homeassistant/components/alexa/entities.py +++ b/homeassistant/components/alexa/entities.py @@ -382,7 +382,6 @@ def async_get_entities(hass, config) -> list[AlexaEntity]: @ENTITY_ADAPTERS.register(alert.DOMAIN) @ENTITY_ADAPTERS.register(automation.DOMAIN) @ENTITY_ADAPTERS.register(group.DOMAIN) -@ENTITY_ADAPTERS.register(input_boolean.DOMAIN) class GenericCapabilities(AlexaEntity): """A generic, on/off device. @@ -405,12 +404,16 @@ class GenericCapabilities(AlexaEntity): ] +@ENTITY_ADAPTERS.register(input_boolean.DOMAIN) @ENTITY_ADAPTERS.register(switch.DOMAIN) class SwitchCapabilities(AlexaEntity): """Class to represent Switch capabilities.""" def default_display_categories(self): """Return the display categories for this entity.""" + if self.entity.domain == input_boolean.DOMAIN: + return [DisplayCategory.OTHER] + device_class = self.entity.attributes.get(ATTR_DEVICE_CLASS) if device_class == switch.SwitchDeviceClass.OUTLET: return [DisplayCategory.SMARTPLUG] @@ -421,6 +424,7 @@ class SwitchCapabilities(AlexaEntity): """Yield the supported interfaces.""" return [ AlexaPowerController(self.entity), + AlexaContactSensor(self.hass, self.entity), AlexaEndpointHealth(self.hass, self.entity), Alexa(self.hass), ] @@ -439,6 +443,8 @@ class ButtonCapabilities(AlexaEntity): """Yield the supported interfaces.""" return [ AlexaSceneController(self.entity, supports_deactivation=False), + AlexaEventDetectionSensor(self.hass, self.entity), + AlexaEndpointHealth(self.hass, self.entity), Alexa(self.hass), ] diff --git a/tests/components/alexa/test_capabilities.py b/tests/components/alexa/test_capabilities.py index 4cccae1f083..3e176b0fb8c 100644 --- a/tests/components/alexa/test_capabilities.py +++ b/tests/components/alexa/test_capabilities.py @@ -846,6 +846,57 @@ 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.""" + hass.states.async_set( + f"{domain}.test_button", "now", {"friendly_name": "Test button"} + ) + + properties = await reported_properties(hass, f"{domain}#test_button") + properties.assert_equal( + "Alexa.EventDetectionSensor", + "humanPresenceDetectionState", + {"value": "DETECTED"}, + ) + + +@pytest.mark.parametrize("domain", ["switch", "input_boolean"]) +async def test_toggle_entities_report_contact_events(hass, domain): + """Test toggles and switches report contact sensor events to trigger routines.""" + hass.states.async_set( + f"{domain}.test_toggle", "on", {"friendly_name": "Test toggle"} + ) + + properties = await reported_properties(hass, f"{domain}#test_toggle") + properties.assert_equal( + "Alexa.PowerController", + "powerState", + "ON", + ) + properties.assert_equal( + "Alexa.ContactSensor", + "detectionState", + "DETECTED", + ) + + hass.states.async_set( + f"{domain}.test_toggle", "off", {"friendly_name": "Test toggle"} + ) + + properties = await reported_properties(hass, f"{domain}#test_toggle") + properties.assert_equal( + "Alexa.PowerController", + "powerState", + "OFF", + ) + properties.assert_equal( + "Alexa.ContactSensor", + "detectionState", + "NOT_DETECTED", + ) + + async def test_get_property_blowup(hass, caplog): """Test we handle a property blowing up.""" hass.states.async_set( diff --git a/tests/components/alexa/test_smart_home.py b/tests/components/alexa/test_smart_home.py index 37888a2c415..0169eeff9d5 100644 --- a/tests/components/alexa/test_smart_home.py +++ b/tests/components/alexa/test_smart_home.py @@ -182,8 +182,12 @@ async def test_switch(hass, events): assert appliance["endpointId"] == "switch#test" assert appliance["displayCategories"][0] == "SWITCH" assert appliance["friendlyName"] == "Test switch" - assert_endpoint_capabilities( - appliance, "Alexa.PowerController", "Alexa.EndpointHealth", "Alexa" + capabilities = assert_endpoint_capabilities( + appliance, + "Alexa.PowerController", + "Alexa.ContactSensor", + "Alexa.EndpointHealth", + "Alexa", ) await assert_power_controller_works( @@ -192,6 +196,14 @@ async def test_switch(hass, events): properties = await reported_properties(hass, "switch#test") properties.assert_equal("Alexa.PowerController", "powerState", "ON") + properties.assert_equal("Alexa.ContactSensor", "detectionState", "DETECTED") + properties.assert_equal("Alexa.EndpointHealth", "connectivity", {"value": "OK"}) + + contact_sensor_capability = get_capability(capabilities, "Alexa.ContactSensor") + assert contact_sensor_capability is not None + properties = contact_sensor_capability["properties"] + assert properties["retrievable"] is True + assert {"name": "detectionState"} in properties["supported"] async def test_outlet(hass, events): @@ -207,7 +219,11 @@ async def test_outlet(hass, events): assert appliance["displayCategories"][0] == "SMARTPLUG" assert appliance["friendlyName"] == "Test switch" assert_endpoint_capabilities( - appliance, "Alexa", "Alexa.PowerController", "Alexa.EndpointHealth" + appliance, + "Alexa", + "Alexa.PowerController", + "Alexa.EndpointHealth", + "Alexa.ContactSensor", ) @@ -335,8 +351,12 @@ async def test_input_boolean(hass): assert appliance["endpointId"] == "input_boolean#test" assert appliance["displayCategories"][0] == "OTHER" assert appliance["friendlyName"] == "Test input boolean" - assert_endpoint_capabilities( - appliance, "Alexa.PowerController", "Alexa.EndpointHealth", "Alexa" + capabilities = assert_endpoint_capabilities( + appliance, + "Alexa.PowerController", + "Alexa.ContactSensor", + "Alexa.EndpointHealth", + "Alexa", ) await assert_power_controller_works( @@ -347,6 +367,17 @@ async def test_input_boolean(hass): "2022-04-19T07:53:05Z", ) + properties = await reported_properties(hass, "input_boolean#test") + properties.assert_equal("Alexa.PowerController", "powerState", "OFF") + properties.assert_equal("Alexa.ContactSensor", "detectionState", "NOT_DETECTED") + properties.assert_equal("Alexa.EndpointHealth", "connectivity", {"value": "OK"}) + + contact_sensor_capability = get_capability(capabilities, "Alexa.ContactSensor") + assert contact_sensor_capability is not None + properties = contact_sensor_capability["properties"] + assert properties["retrievable"] is True + assert {"name": "detectionState"} in properties["supported"] + @freeze_time("2022-04-19 07:53:05") async def test_scene(hass): @@ -4003,7 +4034,11 @@ async def test_button(hass, domain): assert appliance["friendlyName"] == "Ring Doorbell" capabilities = assert_endpoint_capabilities( - appliance, "Alexa.SceneController", "Alexa" + appliance, + "Alexa.SceneController", + "Alexa.EventDetectionSensor", + "Alexa.EndpointHealth", + "Alexa", ) scene_capability = get_capability(capabilities, "Alexa.SceneController") assert scene_capability["supportsDeactivation"] is False @@ -4016,6 +4051,21 @@ async def test_button(hass, domain): "2022-04-19T07:53:05Z", ) + event_detection_capability = get_capability( + capabilities, "Alexa.EventDetectionSensor" + ) + assert event_detection_capability is not None + properties = event_detection_capability["properties"] + assert properties["proactivelyReported"] is True + assert not properties["retrievable"] + assert {"name": "humanPresenceDetectionState"} in properties["supported"] + assert ( + event_detection_capability["configuration"]["detectionModes"]["humanPresence"][ + "supportsNotDetected" + ] + is False + ) + async def test_api_message_sets_authorized(hass): """Test an incoming API messages sets the authorized flag."""