Implement Alexa.EventDetectionSensor for Alexa (#28276)
* Implement Alexa.EventDetectionSensor Interface * Removed references to PR #28218 not yet merged into dev. * Update tests to include Alexa Interface * Guard for `unknown` and `unavailible` states. * Fixed Unnecessary "elif" after "return"
This commit is contained in:
parent
3c86825e25
commit
3db7e8f5e9
5 changed files with 217 additions and 3 deletions
|
@ -1,7 +1,7 @@
|
|||
"""Alexa capabilities."""
|
||||
import logging
|
||||
|
||||
from homeassistant.components import cover, fan, light
|
||||
from homeassistant.components import cover, fan, image_processing, light
|
||||
from homeassistant.components.alarm_control_panel import ATTR_CODE_FORMAT, FORMAT_NUMBER
|
||||
import homeassistant.components.climate.const as climate
|
||||
import homeassistant.components.media_player.const as media_player
|
||||
|
@ -1265,3 +1265,60 @@ class AlexaSeekController(AlexaCapability):
|
|||
def name(self):
|
||||
"""Return the Alexa API name of this interface."""
|
||||
return "Alexa.SeekController"
|
||||
|
||||
|
||||
class AlexaEventDetectionSensor(AlexaCapability):
|
||||
"""Implements Alexa.EventDetectionSensor.
|
||||
|
||||
https://developer.amazon.com/docs/device-apis/alexa-eventdetectionsensor.html
|
||||
"""
|
||||
|
||||
def __init__(self, hass, entity):
|
||||
"""Initialize the entity."""
|
||||
super().__init__(entity)
|
||||
self.hass = hass
|
||||
|
||||
def name(self):
|
||||
"""Return the Alexa API name of this interface."""
|
||||
return "Alexa.EventDetectionSensor"
|
||||
|
||||
def properties_supported(self):
|
||||
"""Return what properties this entity supports."""
|
||||
return [{"name": "humanPresenceDetectionState"}]
|
||||
|
||||
def properties_proactively_reported(self):
|
||||
"""Return True if properties asynchronously reported."""
|
||||
return True
|
||||
|
||||
def get_property(self, name):
|
||||
"""Read and return a property."""
|
||||
if name != "humanPresenceDetectionState":
|
||||
raise UnsupportedProperty(name)
|
||||
|
||||
human_presence = "NOT_DETECTED"
|
||||
state = self.entity.state
|
||||
|
||||
# Return None for unavailable and unknown states.
|
||||
# Allows the Alexa.EndpointHealth Interface to handle the unavailable state in a stateReport.
|
||||
if state in (STATE_UNAVAILABLE, STATE_UNKNOWN, None):
|
||||
return None
|
||||
|
||||
if self.entity.domain == image_processing.DOMAIN:
|
||||
if int(state):
|
||||
human_presence = "DETECTED"
|
||||
elif state == STATE_ON:
|
||||
human_presence = "DETECTED"
|
||||
|
||||
return {"value": human_presence}
|
||||
|
||||
def configuration(self):
|
||||
"""Return supported detection types."""
|
||||
return {
|
||||
"detectionMethods": ["AUDIO", "VIDEO"],
|
||||
"detectionModes": {
|
||||
"humanPresence": {
|
||||
"featureAvailability": "ENABLED",
|
||||
"supportsNotDetected": True,
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ from homeassistant.components import (
|
|||
cover,
|
||||
fan,
|
||||
group,
|
||||
image_processing,
|
||||
input_boolean,
|
||||
light,
|
||||
lock,
|
||||
|
@ -40,6 +41,7 @@ from .capabilities import (
|
|||
AlexaContactSensor,
|
||||
AlexaDoorbellEventSource,
|
||||
AlexaEndpointHealth,
|
||||
AlexaEventDetectionSensor,
|
||||
AlexaInputController,
|
||||
AlexaLockController,
|
||||
AlexaModeController,
|
||||
|
@ -522,6 +524,7 @@ class BinarySensorCapabilities(AlexaEntity):
|
|||
|
||||
TYPE_CONTACT = "contact"
|
||||
TYPE_MOTION = "motion"
|
||||
TYPE_PRESENCE = "presence"
|
||||
|
||||
def default_display_categories(self):
|
||||
"""Return the display categories for this entity."""
|
||||
|
@ -530,6 +533,8 @@ class BinarySensorCapabilities(AlexaEntity):
|
|||
return [DisplayCategory.CONTACT_SENSOR]
|
||||
if sensor_type is self.TYPE_MOTION:
|
||||
return [DisplayCategory.MOTION_SENSOR]
|
||||
if sensor_type is self.TYPE_PRESENCE:
|
||||
return [DisplayCategory.CAMERA]
|
||||
|
||||
def interfaces(self):
|
||||
"""Yield the supported interfaces."""
|
||||
|
@ -538,7 +543,10 @@ class BinarySensorCapabilities(AlexaEntity):
|
|||
yield AlexaContactSensor(self.hass, self.entity)
|
||||
elif sensor_type is self.TYPE_MOTION:
|
||||
yield AlexaMotionSensor(self.hass, self.entity)
|
||||
elif sensor_type is self.TYPE_PRESENCE:
|
||||
yield AlexaEventDetectionSensor(self.hass, self.entity)
|
||||
|
||||
# yield additional interfaces based on specified display category in config.
|
||||
entity_conf = self.config.entity_config.get(self.entity.entity_id, {})
|
||||
if CONF_DISPLAY_CATEGORIES in entity_conf:
|
||||
if entity_conf[CONF_DISPLAY_CATEGORIES] == DisplayCategory.DOORBELL:
|
||||
|
@ -547,6 +555,8 @@ class BinarySensorCapabilities(AlexaEntity):
|
|||
yield AlexaContactSensor(self.hass, self.entity)
|
||||
elif entity_conf[CONF_DISPLAY_CATEGORIES] == DisplayCategory.MOTION_SENSOR:
|
||||
yield AlexaMotionSensor(self.hass, self.entity)
|
||||
elif entity_conf[CONF_DISPLAY_CATEGORIES] == DisplayCategory.CAMERA:
|
||||
yield AlexaEventDetectionSensor(self.hass, self.entity)
|
||||
|
||||
yield AlexaEndpointHealth(self.hass, self.entity)
|
||||
yield Alexa(self.hass)
|
||||
|
@ -554,11 +564,20 @@ class BinarySensorCapabilities(AlexaEntity):
|
|||
def get_type(self):
|
||||
"""Return the type of binary sensor."""
|
||||
attrs = self.entity.attributes
|
||||
if attrs.get(ATTR_DEVICE_CLASS) in ("door", "garage_door", "opening", "window"):
|
||||
if attrs.get(ATTR_DEVICE_CLASS) in (
|
||||
binary_sensor.DEVICE_CLASS_DOOR,
|
||||
binary_sensor.DEVICE_CLASS_GARAGE_DOOR,
|
||||
binary_sensor.DEVICE_CLASS_OPENING,
|
||||
binary_sensor.DEVICE_CLASS_WINDOW,
|
||||
):
|
||||
return self.TYPE_CONTACT
|
||||
if attrs.get(ATTR_DEVICE_CLASS) == "motion":
|
||||
|
||||
if attrs.get(ATTR_DEVICE_CLASS) == binary_sensor.DEVICE_CLASS_MOTION:
|
||||
return self.TYPE_MOTION
|
||||
|
||||
if attrs.get(ATTR_DEVICE_CLASS) == binary_sensor.DEVICE_CLASS_PRESENCE:
|
||||
return self.TYPE_PRESENCE
|
||||
|
||||
|
||||
@ENTITY_ADAPTERS.register(alarm_control_panel.DOMAIN)
|
||||
class AlarmControlPanelCapabilities(AlexaEntity):
|
||||
|
@ -574,3 +593,17 @@ class AlarmControlPanelCapabilities(AlexaEntity):
|
|||
yield AlexaSecurityPanelController(self.hass, self.entity)
|
||||
yield AlexaEndpointHealth(self.hass, self.entity)
|
||||
yield Alexa(self.hass)
|
||||
|
||||
|
||||
@ENTITY_ADAPTERS.register(image_processing.DOMAIN)
|
||||
class ImageProcessingCapabilities(AlexaEntity):
|
||||
"""Class to represent image_processing capabilities."""
|
||||
|
||||
def default_display_categories(self):
|
||||
"""Return the display categories for this entity."""
|
||||
return [DisplayCategory.CAMERA]
|
||||
|
||||
def interfaces(self):
|
||||
"""Yield the supported interfaces."""
|
||||
yield AlexaEventDetectionSensor(self.hass, self.entity)
|
||||
yield AlexaEndpointHealth(self.hass, self.entity)
|
||||
|
|
|
@ -17,6 +17,7 @@ class MockConfig(config.AbstractConfig):
|
|||
"binary_sensor.test_doorbell": {"display_categories": "DOORBELL"},
|
||||
"binary_sensor.test_contact_forced": {"display_categories": "CONTACT_SENSOR"},
|
||||
"binary_sensor.test_motion_forced": {"display_categories": "MOTION_SENSOR"},
|
||||
"binary_sensor.test_motion_camera_event": {"display_categories": "CAMERA"},
|
||||
}
|
||||
|
||||
@property
|
||||
|
|
|
@ -667,3 +667,45 @@ async def test_report_playback_state(hass):
|
|||
properties.assert_equal(
|
||||
"Alexa.PlaybackStateReporter", "playbackState", {"state": "STOPPED"}
|
||||
)
|
||||
|
||||
|
||||
async def test_report_image_processing(hass):
|
||||
"""Test EventDetectionSensor implements humanPresenceDetectionState property."""
|
||||
hass.states.async_set(
|
||||
"image_processing.test_face",
|
||||
0,
|
||||
{
|
||||
"friendly_name": "Test face",
|
||||
"device_class": "face",
|
||||
"faces": [],
|
||||
"total_faces": 0,
|
||||
},
|
||||
)
|
||||
|
||||
properties = await reported_properties(hass, "image_processing#test_face")
|
||||
properties.assert_equal(
|
||||
"Alexa.EventDetectionSensor",
|
||||
"humanPresenceDetectionState",
|
||||
{"value": "NOT_DETECTED"},
|
||||
)
|
||||
|
||||
hass.states.async_set(
|
||||
"image_processing.test_classifier",
|
||||
3,
|
||||
{
|
||||
"friendly_name": "Test classifier",
|
||||
"device_class": "face",
|
||||
"faces": [
|
||||
{"confidence": 98.34, "name": "Hans", "age": 16.0, "gender": "male"},
|
||||
{"name": "Helena", "age": 28.0, "gender": "female"},
|
||||
{"confidence": 62.53, "name": "Luna"},
|
||||
],
|
||||
"total_faces": 3,
|
||||
},
|
||||
)
|
||||
properties = await reported_properties(hass, "image_processing#test_classifier")
|
||||
properties.assert_equal(
|
||||
"Alexa.EventDetectionSensor",
|
||||
"humanPresenceDetectionState",
|
||||
{"value": "DETECTED"},
|
||||
)
|
||||
|
|
|
@ -2386,3 +2386,84 @@ async def test_cover_position(hass):
|
|||
assert properties["name"] == "mode"
|
||||
assert properties["namespace"] == "Alexa.ModeController"
|
||||
assert properties["value"] == "position.open"
|
||||
|
||||
|
||||
async def test_image_processing(hass):
|
||||
"""Test image_processing discovery as event detection."""
|
||||
device = (
|
||||
"image_processing.test_face",
|
||||
0,
|
||||
{
|
||||
"friendly_name": "Test face",
|
||||
"device_class": "face",
|
||||
"faces": [],
|
||||
"total_faces": 0,
|
||||
},
|
||||
)
|
||||
appliance = await discovery_test(device, hass)
|
||||
|
||||
assert appliance["endpointId"] == "image_processing#test_face"
|
||||
assert appliance["displayCategories"][0] == "CAMERA"
|
||||
assert appliance["friendlyName"] == "Test face"
|
||||
|
||||
assert_endpoint_capabilities(
|
||||
appliance, "Alexa.EventDetectionSensor", "Alexa.EndpointHealth"
|
||||
)
|
||||
|
||||
|
||||
async def test_motion_sensor_event_detection(hass):
|
||||
"""Test motion sensor with EventDetectionSensor discovery."""
|
||||
device = (
|
||||
"binary_sensor.test_motion_camera_event",
|
||||
"off",
|
||||
{"friendly_name": "Test motion camera event", "device_class": "motion"},
|
||||
)
|
||||
appliance = await discovery_test(device, hass)
|
||||
|
||||
assert appliance["endpointId"] == "binary_sensor#test_motion_camera_event"
|
||||
assert appliance["displayCategories"][0] == "CAMERA"
|
||||
assert appliance["friendlyName"] == "Test motion camera event"
|
||||
|
||||
capabilities = assert_endpoint_capabilities(
|
||||
appliance,
|
||||
"Alexa",
|
||||
"Alexa.MotionSensor",
|
||||
"Alexa.EventDetectionSensor",
|
||||
"Alexa.EndpointHealth",
|
||||
)
|
||||
|
||||
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"]
|
||||
|
||||
|
||||
async def test_presence_sensor(hass):
|
||||
"""Test presence sensor."""
|
||||
device = (
|
||||
"binary_sensor.test_presence_sensor",
|
||||
"off",
|
||||
{"friendly_name": "Test presence sensor", "device_class": "presence"},
|
||||
)
|
||||
appliance = await discovery_test(device, hass)
|
||||
|
||||
assert appliance["endpointId"] == "binary_sensor#test_presence_sensor"
|
||||
assert appliance["displayCategories"][0] == "CAMERA"
|
||||
assert appliance["friendlyName"] == "Test presence sensor"
|
||||
|
||||
capabilities = assert_endpoint_capabilities(
|
||||
appliance, "Alexa", "Alexa.EventDetectionSensor", "Alexa.EndpointHealth"
|
||||
)
|
||||
|
||||
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"]
|
||||
|
|
Loading…
Add table
Reference in a new issue