Add nest device triggers for camera and doorbell events (#43548)
parent
2cbb93be43
commit
945a0a9f7e
13
.coveragerc
13
.coveragerc
|
@ -572,7 +572,18 @@ omit =
|
|||
homeassistant/components/neato/vacuum.py
|
||||
homeassistant/components/nederlandse_spoorwegen/sensor.py
|
||||
homeassistant/components/nello/lock.py
|
||||
homeassistant/components/nest/*
|
||||
homeassistant/components/nest/__init__.py
|
||||
homeassistant/components/nest/api.py
|
||||
homeassistant/components/nest/binary_sensor.py
|
||||
homeassistant/components/nest/camera.py
|
||||
homeassistant/components/nest/camera_legacy.py
|
||||
homeassistant/components/nest/camera_sdm.py
|
||||
homeassistant/components/nest/climate.py
|
||||
homeassistant/components/nest/climate_legacy.py
|
||||
homeassistant/components/nest/climate_sdm.py
|
||||
homeassistant/components/nest/local_auth.py
|
||||
homeassistant/components/nest/sensor.py
|
||||
homeassistant/components/nest/sensor_legacy.py
|
||||
homeassistant/components/netatmo/__init__.py
|
||||
homeassistant/components/netatmo/api.py
|
||||
homeassistant/components/netatmo/camera.py
|
||||
|
|
|
@ -5,14 +5,7 @@ from datetime import datetime, timedelta
|
|||
import logging
|
||||
import threading
|
||||
|
||||
from google_nest_sdm.event import (
|
||||
AsyncEventCallback,
|
||||
CameraMotionEvent,
|
||||
CameraPersonEvent,
|
||||
CameraSoundEvent,
|
||||
DoorbellChimeEvent,
|
||||
EventMessage,
|
||||
)
|
||||
from google_nest_sdm.event import AsyncEventCallback, EventMessage
|
||||
from google_nest_sdm.exceptions import GoogleNestException
|
||||
from google_nest_sdm.google_nest_subscriber import GoogleNestSubscriber
|
||||
from nest import Nest
|
||||
|
@ -50,24 +43,19 @@ from . import api, config_flow, local_auth
|
|||
from .const import (
|
||||
API_URL,
|
||||
DATA_SDM,
|
||||
DATA_SUBSCRIBER,
|
||||
DOMAIN,
|
||||
OAUTH2_AUTHORIZE,
|
||||
OAUTH2_TOKEN,
|
||||
SIGNAL_NEST_UPDATE,
|
||||
)
|
||||
from .events import EVENT_NAME_MAP, NEST_EVENT
|
||||
|
||||
_CONFIGURING = {}
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CONF_PROJECT_ID = "project_id"
|
||||
CONF_SUBSCRIBER_ID = "subscriber_id"
|
||||
NEST_EVENT = "nest_event"
|
||||
EVENT_TRAIT_MAP = {
|
||||
DoorbellChimeEvent.NAME: "DoorbellChime",
|
||||
CameraMotionEvent.NAME: "CameraMotion",
|
||||
CameraPersonEvent.NAME: "CameraPerson",
|
||||
CameraSoundEvent.NAME: "CameraSound",
|
||||
}
|
||||
|
||||
|
||||
# Configuration for the legacy nest API
|
||||
|
@ -206,11 +194,12 @@ class SignalUpdateCallback(AsyncEventCallback):
|
|||
_LOGGER.debug("Ignoring event for unregistered device '%s'", device_id)
|
||||
return
|
||||
for event in events:
|
||||
if event not in EVENT_TRAIT_MAP:
|
||||
event_type = EVENT_NAME_MAP.get(event)
|
||||
if not event_type:
|
||||
continue
|
||||
message = {
|
||||
"device_id": device_entry.id,
|
||||
"type": EVENT_TRAIT_MAP[event],
|
||||
"type": event_type,
|
||||
}
|
||||
self._hass.bus.async_fire(NEST_EVENT, message)
|
||||
|
||||
|
@ -254,7 +243,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
|
|||
subscriber.stop_async()
|
||||
raise ConfigEntryNotReady from err
|
||||
|
||||
hass.data[DOMAIN][entry.entry_id] = subscriber
|
||||
hass.data[DOMAIN][DATA_SUBSCRIBER] = subscriber
|
||||
|
||||
for component in PLATFORMS:
|
||||
hass.async_create_task(
|
||||
|
@ -270,7 +259,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
|
|||
# Legacy API
|
||||
return True
|
||||
|
||||
subscriber = hass.data[DOMAIN][entry.entry_id]
|
||||
subscriber = hass.data[DOMAIN][DATA_SUBSCRIBER]
|
||||
subscriber.stop_async()
|
||||
unload_ok = all(
|
||||
await asyncio.gather(
|
||||
|
@ -281,7 +270,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
|
|||
)
|
||||
)
|
||||
if unload_ok:
|
||||
hass.data[DOMAIN].pop(entry.entry_id)
|
||||
hass.data[DOMAIN].pop(DATA_SUBSCRIBER)
|
||||
|
||||
return unload_ok
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ from homeassistant.helpers.event import async_track_point_in_utc_time
|
|||
from homeassistant.helpers.typing import HomeAssistantType
|
||||
from homeassistant.util.dt import utcnow
|
||||
|
||||
from .const import DOMAIN, SIGNAL_NEST_UPDATE
|
||||
from .const import DATA_SUBSCRIBER, DOMAIN, SIGNAL_NEST_UPDATE
|
||||
from .device_info import DeviceInfo
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
@ -32,7 +32,7 @@ async def async_setup_sdm_entry(
|
|||
) -> None:
|
||||
"""Set up the cameras."""
|
||||
|
||||
subscriber = hass.data[DOMAIN][entry.entry_id]
|
||||
subscriber = hass.data[DOMAIN][DATA_SUBSCRIBER]
|
||||
try:
|
||||
device_manager = await subscriber.async_get_device_manager()
|
||||
except GoogleNestException as err:
|
||||
|
|
|
@ -39,7 +39,7 @@ from homeassistant.exceptions import PlatformNotReady
|
|||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.typing import HomeAssistantType
|
||||
|
||||
from .const import DOMAIN, SIGNAL_NEST_UPDATE
|
||||
from .const import DATA_SUBSCRIBER, DOMAIN, SIGNAL_NEST_UPDATE
|
||||
from .device_info import DeviceInfo
|
||||
|
||||
# Mapping for sdm.devices.traits.ThermostatMode mode field
|
||||
|
@ -81,7 +81,7 @@ async def async_setup_sdm_entry(
|
|||
) -> None:
|
||||
"""Set up the client entities."""
|
||||
|
||||
subscriber = hass.data[DOMAIN][entry.entry_id]
|
||||
subscriber = hass.data[DOMAIN][DATA_SUBSCRIBER]
|
||||
try:
|
||||
device_manager = await subscriber.async_get_device_manager()
|
||||
except GoogleNestException as err:
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
DOMAIN = "nest"
|
||||
DATA_SDM = "sdm"
|
||||
DATA_SUBSCRIBER = "subscriber"
|
||||
|
||||
SIGNAL_NEST_UPDATE = "nest_update"
|
||||
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
"""Provides device automations for Nest."""
|
||||
import logging
|
||||
from typing import List
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.automation import AutomationActionType
|
||||
from homeassistant.components.device_automation import TRIGGER_BASE_SCHEMA
|
||||
from homeassistant.components.device_automation.exceptions import (
|
||||
InvalidDeviceAutomationConfig,
|
||||
)
|
||||
from homeassistant.components.homeassistant.triggers import event as event_trigger
|
||||
from homeassistant.const import CONF_DEVICE_ID, CONF_DOMAIN, CONF_PLATFORM, CONF_TYPE
|
||||
from homeassistant.core import CALLBACK_TYPE, HomeAssistant
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from .const import DATA_SUBSCRIBER, DOMAIN
|
||||
from .events import DEVICE_TRAIT_TRIGGER_MAP, NEST_EVENT
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEVICE = "device"
|
||||
|
||||
TRIGGER_TYPES = set(DEVICE_TRAIT_TRIGGER_MAP.values())
|
||||
|
||||
TRIGGER_SCHEMA = TRIGGER_BASE_SCHEMA.extend(
|
||||
{
|
||||
vol.Required(CONF_TYPE): vol.In(TRIGGER_TYPES),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def async_get_nest_device_id(hass: HomeAssistant, device_id: str) -> str:
|
||||
"""Get the nest API device_id from the HomeAssistant device_id."""
|
||||
device_registry = await hass.helpers.device_registry.async_get_registry()
|
||||
device = device_registry.async_get(device_id)
|
||||
for (domain, unique_id) in device.identifiers:
|
||||
if domain == DOMAIN:
|
||||
return unique_id
|
||||
return None
|
||||
|
||||
|
||||
async def async_get_device_trigger_types(
|
||||
hass: HomeAssistant, nest_device_id: str
|
||||
) -> List[str]:
|
||||
"""List event triggers supported for a Nest device."""
|
||||
# All devices should have already been loaded so any failures here are
|
||||
# "shouldn't happen" cases
|
||||
subscriber = hass.data[DOMAIN][DATA_SUBSCRIBER]
|
||||
device_manager = await subscriber.async_get_device_manager()
|
||||
nest_device = device_manager.devices.get(nest_device_id)
|
||||
if not nest_device:
|
||||
raise InvalidDeviceAutomationConfig(f"Nest device not found {nest_device_id}")
|
||||
|
||||
# Determine the set of event types based on the supported device traits
|
||||
trigger_types = []
|
||||
for trait in nest_device.traits.keys():
|
||||
trigger_type = DEVICE_TRAIT_TRIGGER_MAP.get(trait)
|
||||
if trigger_type:
|
||||
trigger_types.append(trigger_type)
|
||||
return trigger_types
|
||||
|
||||
|
||||
async def async_get_triggers(hass: HomeAssistant, device_id: str) -> List[dict]:
|
||||
"""List device triggers for a Nest device."""
|
||||
nest_device_id = await async_get_nest_device_id(hass, device_id)
|
||||
if not nest_device_id:
|
||||
raise InvalidDeviceAutomationConfig(f"Device not found {device_id}")
|
||||
trigger_types = await async_get_device_trigger_types(hass, nest_device_id)
|
||||
return [
|
||||
{
|
||||
CONF_PLATFORM: DEVICE,
|
||||
CONF_DEVICE_ID: device_id,
|
||||
CONF_DOMAIN: DOMAIN,
|
||||
CONF_TYPE: trigger_type,
|
||||
}
|
||||
for trigger_type in trigger_types
|
||||
]
|
||||
|
||||
|
||||
async def async_attach_trigger(
|
||||
hass: HomeAssistant,
|
||||
config: ConfigType,
|
||||
action: AutomationActionType,
|
||||
automation_info: dict,
|
||||
) -> CALLBACK_TYPE:
|
||||
"""Attach a trigger."""
|
||||
config = TRIGGER_SCHEMA(config)
|
||||
event_config = event_trigger.TRIGGER_SCHEMA(
|
||||
{
|
||||
event_trigger.CONF_PLATFORM: "event",
|
||||
event_trigger.CONF_EVENT_TYPE: NEST_EVENT,
|
||||
event_trigger.CONF_EVENT_DATA: {
|
||||
CONF_DEVICE_ID: config[CONF_DEVICE_ID],
|
||||
CONF_TYPE: config[CONF_TYPE],
|
||||
},
|
||||
}
|
||||
)
|
||||
return await event_trigger.async_attach_trigger(
|
||||
hass, event_config, action, automation_info, platform_type="device"
|
||||
)
|
|
@ -0,0 +1,49 @@
|
|||
"""Library from Pub/sub messages, events and device triggers."""
|
||||
|
||||
from google_nest_sdm.camera_traits import (
|
||||
CameraMotionTrait,
|
||||
CameraPersonTrait,
|
||||
CameraSoundTrait,
|
||||
)
|
||||
from google_nest_sdm.doorbell_traits import DoorbellChimeTrait
|
||||
from google_nest_sdm.event import (
|
||||
CameraMotionEvent,
|
||||
CameraPersonEvent,
|
||||
CameraSoundEvent,
|
||||
DoorbellChimeEvent,
|
||||
)
|
||||
|
||||
NEST_EVENT = "nest_event"
|
||||
# The nest_event namespace will fire events that are triggered from messages
|
||||
# received via the Pub/Sub subscriber.
|
||||
#
|
||||
# An example event data payload:
|
||||
# {
|
||||
# "device_id": "enterprises/some/device/identifier"
|
||||
# "event_type": "camera_motion"
|
||||
# }
|
||||
#
|
||||
# The following event types are fired:
|
||||
EVENT_DOORBELL_CHIME = "doorbell_chime"
|
||||
EVENT_CAMERA_MOTION = "camera_motion"
|
||||
EVENT_CAMERA_PERSON = "camera_person"
|
||||
EVENT_CAMERA_SOUND = "camera_sound"
|
||||
|
||||
# Mapping of supported device traits to home assistant event types. Devices
|
||||
# that support these traits will generate Pub/Sub event messages in
|
||||
# the EVENT_NAME_MAP
|
||||
DEVICE_TRAIT_TRIGGER_MAP = {
|
||||
DoorbellChimeTrait.NAME: EVENT_DOORBELL_CHIME,
|
||||
CameraMotionTrait.NAME: EVENT_CAMERA_MOTION,
|
||||
CameraPersonTrait.NAME: EVENT_CAMERA_PERSON,
|
||||
CameraSoundTrait.NAME: EVENT_CAMERA_SOUND,
|
||||
}
|
||||
|
||||
# Mapping of incoming SDM Pub/Sub event message types to the home assistant
|
||||
# event type to fire.
|
||||
EVENT_NAME_MAP = {
|
||||
DoorbellChimeEvent.NAME: EVENT_DOORBELL_CHIME,
|
||||
CameraMotionEvent.NAME: EVENT_CAMERA_MOTION,
|
||||
CameraPersonEvent.NAME: EVENT_CAMERA_PERSON,
|
||||
CameraSoundEvent.NAME: EVENT_CAMERA_SOUND,
|
||||
}
|
|
@ -19,7 +19,7 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
|||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.helpers.typing import HomeAssistantType
|
||||
|
||||
from .const import DOMAIN, SIGNAL_NEST_UPDATE
|
||||
from .const import DATA_SUBSCRIBER, DOMAIN, SIGNAL_NEST_UPDATE
|
||||
from .device_info import DeviceInfo
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
@ -38,7 +38,7 @@ async def async_setup_sdm_entry(
|
|||
) -> None:
|
||||
"""Set up the sensors."""
|
||||
|
||||
subscriber = hass.data[DOMAIN][entry.entry_id]
|
||||
subscriber = hass.data[DOMAIN][DATA_SUBSCRIBER]
|
||||
try:
|
||||
device_manager = await subscriber.async_get_device_manager()
|
||||
except GoogleNestException as err:
|
||||
|
|
|
@ -7,12 +7,16 @@
|
|||
"init": {
|
||||
"title": "Authentication Provider",
|
||||
"description": "[%key:common::config_flow::title::oauth2_pick_implementation%]",
|
||||
"data": { "flow_impl": "Provider" }
|
||||
"data": {
|
||||
"flow_impl": "Provider"
|
||||
}
|
||||
},
|
||||
"link": {
|
||||
"title": "Link Nest Account",
|
||||
"description": "To link your Nest account, [authorize your account]({url}).\n\nAfter authorization, copy-paste the provided pin code below.",
|
||||
"data": { "code": "[%key:common::config_flow::data::pin%]" }
|
||||
"data": {
|
||||
"code": "[%key:common::config_flow::data::pin%]"
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
|
@ -31,5 +35,13 @@
|
|||
"create_entry": {
|
||||
"default": "[%key:common::config_flow::create_entry::authenticated%]"
|
||||
}
|
||||
},
|
||||
"device_automation": {
|
||||
"trigger_type": {
|
||||
"camera_person": "Person detected",
|
||||
"camera_motion": "Motion detected",
|
||||
"camera_sound": "Sound detected",
|
||||
"doorbell_chime": "Doorbell pressed"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,5 +36,13 @@
|
|||
"title": "Pick Authentication Method"
|
||||
}
|
||||
}
|
||||
},
|
||||
"device_automation": {
|
||||
"trigger_type": {
|
||||
"camera_person": "Person detected",
|
||||
"camera_motion": "Motion detected",
|
||||
"camera_sound": "Sound detected",
|
||||
"doorbell_chime": "Doorbell pressed"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,313 @@
|
|||
"""The tests for Nest device triggers."""
|
||||
from google_nest_sdm.device import Device
|
||||
from google_nest_sdm.event import EventMessage
|
||||
import pytest
|
||||
|
||||
import homeassistant.components.automation as automation
|
||||
from homeassistant.components.device_automation.exceptions import (
|
||||
InvalidDeviceAutomationConfig,
|
||||
)
|
||||
from homeassistant.components.nest import DOMAIN, NEST_EVENT
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from .common import async_setup_sdm_platform
|
||||
|
||||
from tests.common import (
|
||||
assert_lists_same,
|
||||
async_get_device_automations,
|
||||
async_mock_service,
|
||||
)
|
||||
|
||||
DEVICE_ID = "some-device-id"
|
||||
DEVICE_NAME = "My Camera"
|
||||
DATA_MESSAGE = {"message": "service-called"}
|
||||
|
||||
|
||||
def make_camera(device_id, name=DEVICE_NAME, traits={}):
|
||||
"""Create a nest camera."""
|
||||
traits = traits.copy()
|
||||
traits.update(
|
||||
{
|
||||
"sdm.devices.traits.Info": {
|
||||
"customName": name,
|
||||
},
|
||||
"sdm.devices.traits.CameraLiveStream": {
|
||||
"maxVideoResolution": {
|
||||
"width": 640,
|
||||
"height": 480,
|
||||
},
|
||||
"videoCodecs": ["H264"],
|
||||
"audioCodecs": ["AAC"],
|
||||
},
|
||||
}
|
||||
)
|
||||
return Device.MakeDevice(
|
||||
{
|
||||
"name": device_id,
|
||||
"type": "sdm.devices.types.CAMERA",
|
||||
"traits": traits,
|
||||
},
|
||||
auth=None,
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_camera(hass, devices=None):
|
||||
"""Set up the platform and prerequisites for testing available triggers."""
|
||||
if not devices:
|
||||
devices = {DEVICE_ID: make_camera(device_id=DEVICE_ID)}
|
||||
return await async_setup_sdm_platform(hass, "camera", devices)
|
||||
|
||||
|
||||
async def setup_automation(hass, device_id, trigger_type):
|
||||
"""Set up an automation trigger for testing triggering."""
|
||||
return await async_setup_component(
|
||||
hass,
|
||||
automation.DOMAIN,
|
||||
{
|
||||
automation.DOMAIN: [
|
||||
{
|
||||
"trigger": {
|
||||
"platform": "device",
|
||||
"domain": DOMAIN,
|
||||
"device_id": device_id,
|
||||
"type": trigger_type,
|
||||
},
|
||||
"action": {
|
||||
"service": "test.automation",
|
||||
"data": DATA_MESSAGE,
|
||||
},
|
||||
},
|
||||
]
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def calls(hass):
|
||||
"""Track calls to a mock service."""
|
||||
return async_mock_service(hass, "test", "automation")
|
||||
|
||||
|
||||
async def test_get_triggers(hass):
|
||||
"""Test we get the expected triggers from a nest."""
|
||||
camera = make_camera(
|
||||
device_id=DEVICE_ID,
|
||||
traits={
|
||||
"sdm.devices.traits.CameraMotion": {},
|
||||
"sdm.devices.traits.CameraPerson": {},
|
||||
},
|
||||
)
|
||||
await async_setup_camera(hass, {DEVICE_ID: camera})
|
||||
|
||||
device_registry = await hass.helpers.device_registry.async_get_registry()
|
||||
device_entry = device_registry.async_get_device(
|
||||
{("nest", DEVICE_ID)}, connections={}
|
||||
)
|
||||
|
||||
expected_triggers = [
|
||||
{
|
||||
"platform": "device",
|
||||
"domain": DOMAIN,
|
||||
"type": "camera_motion",
|
||||
"device_id": device_entry.id,
|
||||
},
|
||||
{
|
||||
"platform": "device",
|
||||
"domain": DOMAIN,
|
||||
"type": "camera_person",
|
||||
"device_id": device_entry.id,
|
||||
},
|
||||
]
|
||||
triggers = await async_get_device_automations(hass, "trigger", device_entry.id)
|
||||
assert_lists_same(triggers, expected_triggers)
|
||||
|
||||
|
||||
async def test_multiple_devices(hass):
|
||||
"""Test we get the expected triggers from a nest."""
|
||||
camera1 = make_camera(
|
||||
device_id="device-id-1",
|
||||
name="Camera 1",
|
||||
traits={
|
||||
"sdm.devices.traits.CameraSound": {},
|
||||
},
|
||||
)
|
||||
camera2 = make_camera(
|
||||
device_id="device-id-2",
|
||||
name="Camera 2",
|
||||
traits={
|
||||
"sdm.devices.traits.DoorbellChime": {},
|
||||
},
|
||||
)
|
||||
await async_setup_camera(hass, {"device-id-1": camera1, "device-id-2": camera2})
|
||||
|
||||
registry = await hass.helpers.entity_registry.async_get_registry()
|
||||
entry1 = registry.async_get("camera.camera_1")
|
||||
assert entry1.unique_id == "device-id-1-camera"
|
||||
entry2 = registry.async_get("camera.camera_2")
|
||||
assert entry2.unique_id == "device-id-2-camera"
|
||||
|
||||
triggers = await async_get_device_automations(hass, "trigger", entry1.device_id)
|
||||
assert len(triggers) == 1
|
||||
assert {
|
||||
"platform": "device",
|
||||
"domain": DOMAIN,
|
||||
"type": "camera_sound",
|
||||
"device_id": entry1.device_id,
|
||||
} == triggers[0]
|
||||
|
||||
triggers = await async_get_device_automations(hass, "trigger", entry2.device_id)
|
||||
assert len(triggers) == 1
|
||||
assert {
|
||||
"platform": "device",
|
||||
"domain": DOMAIN,
|
||||
"type": "doorbell_chime",
|
||||
"device_id": entry2.device_id,
|
||||
} == triggers[0]
|
||||
|
||||
|
||||
async def test_triggers_for_invalid_device_id(hass):
|
||||
"""Get triggers for a device not found in the API."""
|
||||
camera = make_camera(
|
||||
device_id=DEVICE_ID,
|
||||
traits={
|
||||
"sdm.devices.traits.CameraMotion": {},
|
||||
"sdm.devices.traits.CameraPerson": {},
|
||||
},
|
||||
)
|
||||
await async_setup_camera(hass, {DEVICE_ID: camera})
|
||||
|
||||
device_registry = await hass.helpers.device_registry.async_get_registry()
|
||||
device_entry = device_registry.async_get_device(
|
||||
{("nest", DEVICE_ID)}, connections={}
|
||||
)
|
||||
assert device_entry is not None
|
||||
|
||||
# Create an additional device that does not exist. Fetching supported
|
||||
# triggers for an unknown device will fail.
|
||||
assert len(device_entry.config_entries) == 1
|
||||
config_entry_id = next(iter(device_entry.config_entries))
|
||||
device_entry_2 = device_registry.async_get_or_create(
|
||||
config_entry_id=config_entry_id, identifiers={(DOMAIN, "some-unknown-nest-id")}
|
||||
)
|
||||
assert device_entry_2 is not None
|
||||
|
||||
with pytest.raises(InvalidDeviceAutomationConfig):
|
||||
await async_get_device_automations(hass, "trigger", device_entry_2.id)
|
||||
|
||||
|
||||
async def test_no_triggers(hass):
|
||||
"""Test we get the expected triggers from a nest."""
|
||||
camera = make_camera(device_id=DEVICE_ID, traits={})
|
||||
await async_setup_camera(hass, {DEVICE_ID: camera})
|
||||
|
||||
registry = await hass.helpers.entity_registry.async_get_registry()
|
||||
entry = registry.async_get("camera.my_camera")
|
||||
assert entry.unique_id == "some-device-id-camera"
|
||||
|
||||
triggers = await async_get_device_automations(hass, "trigger", entry.device_id)
|
||||
assert [] == triggers
|
||||
|
||||
|
||||
async def test_fires_on_camera_motion(hass, calls):
|
||||
"""Test camera_motion triggers firing."""
|
||||
assert await setup_automation(hass, DEVICE_ID, "camera_motion")
|
||||
|
||||
message = {"device_id": DEVICE_ID, "type": "camera_motion"}
|
||||
hass.bus.async_fire(NEST_EVENT, message)
|
||||
await hass.async_block_till_done()
|
||||
assert len(calls) == 1
|
||||
assert calls[0].data == DATA_MESSAGE
|
||||
|
||||
|
||||
async def test_fires_on_camera_person(hass, calls):
|
||||
"""Test camera_person triggers firing."""
|
||||
assert await setup_automation(hass, DEVICE_ID, "camera_person")
|
||||
|
||||
message = {"device_id": DEVICE_ID, "type": "camera_person"}
|
||||
hass.bus.async_fire(NEST_EVENT, message)
|
||||
await hass.async_block_till_done()
|
||||
assert len(calls) == 1
|
||||
assert calls[0].data == DATA_MESSAGE
|
||||
|
||||
|
||||
async def test_fires_on_camera_sound(hass, calls):
|
||||
"""Test camera_person triggers firing."""
|
||||
assert await setup_automation(hass, DEVICE_ID, "camera_sound")
|
||||
|
||||
message = {"device_id": DEVICE_ID, "type": "camera_sound"}
|
||||
hass.bus.async_fire(NEST_EVENT, message)
|
||||
await hass.async_block_till_done()
|
||||
assert len(calls) == 1
|
||||
assert calls[0].data == DATA_MESSAGE
|
||||
|
||||
|
||||
async def test_fires_on_doorbell_chime(hass, calls):
|
||||
"""Test doorbell_chime triggers firing."""
|
||||
assert await setup_automation(hass, DEVICE_ID, "doorbell_chime")
|
||||
|
||||
message = {"device_id": DEVICE_ID, "type": "doorbell_chime"}
|
||||
hass.bus.async_fire(NEST_EVENT, message)
|
||||
await hass.async_block_till_done()
|
||||
assert len(calls) == 1
|
||||
assert calls[0].data == DATA_MESSAGE
|
||||
|
||||
|
||||
async def test_trigger_for_wrong_device_id(hass, calls):
|
||||
"""Test for turn_on and turn_off triggers firing."""
|
||||
assert await setup_automation(hass, DEVICE_ID, "camera_motion")
|
||||
|
||||
message = {"device_id": "wrong-device-id", "type": "camera_motion"}
|
||||
hass.bus.async_fire(NEST_EVENT, message)
|
||||
await hass.async_block_till_done()
|
||||
assert len(calls) == 0
|
||||
|
||||
|
||||
async def test_trigger_for_wrong_event_type(hass, calls):
|
||||
"""Test for turn_on and turn_off triggers firing."""
|
||||
assert await setup_automation(hass, DEVICE_ID, "camera_motion")
|
||||
|
||||
message = {"device_id": DEVICE_ID, "type": "wrong-event-type"}
|
||||
hass.bus.async_fire(NEST_EVENT, message)
|
||||
await hass.async_block_till_done()
|
||||
assert len(calls) == 0
|
||||
|
||||
|
||||
async def test_subscriber_automation(hass, calls):
|
||||
"""Test end to end subscriber triggers automation."""
|
||||
camera = make_camera(
|
||||
device_id=DEVICE_ID,
|
||||
traits={
|
||||
"sdm.devices.traits.CameraMotion": {},
|
||||
},
|
||||
)
|
||||
subscriber = await async_setup_camera(hass, {DEVICE_ID: camera})
|
||||
|
||||
device_registry = await hass.helpers.device_registry.async_get_registry()
|
||||
device_entry = device_registry.async_get_device(
|
||||
{("nest", DEVICE_ID)}, connections={}
|
||||
)
|
||||
|
||||
assert await setup_automation(hass, device_entry.id, "camera_motion")
|
||||
|
||||
# Simulate a pubsub message received by the subscriber with a motion event
|
||||
event = EventMessage(
|
||||
{
|
||||
"eventId": "some-event-id",
|
||||
"timestamp": "2019-01-01T00:00:01Z",
|
||||
"resourceUpdate": {
|
||||
"name": DEVICE_ID,
|
||||
"events": {
|
||||
"sdm.devices.events.CameraMotion.Motion": {
|
||||
"eventSessionId": "CjY5Y3VKaTZwR3o4Y19YbTVfMF...",
|
||||
"eventId": "FWWVQVUdGNUlTU2V4MGV2aTNXV...",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
auth=None,
|
||||
)
|
||||
await subscriber.async_receive_event(event)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(calls) == 1
|
||||
assert calls[0].data == DATA_MESSAGE
|
|
@ -110,7 +110,7 @@ async def test_doorbell_chime_event(hass):
|
|||
assert len(events) == 1
|
||||
assert events[0].data == {
|
||||
"device_id": entry.device_id,
|
||||
"type": "DoorbellChime",
|
||||
"type": "doorbell_chime",
|
||||
}
|
||||
|
||||
|
||||
|
@ -134,7 +134,7 @@ async def test_camera_motion_event(hass):
|
|||
assert len(events) == 1
|
||||
assert events[0].data == {
|
||||
"device_id": entry.device_id,
|
||||
"type": "CameraMotion",
|
||||
"type": "camera_motion",
|
||||
}
|
||||
|
||||
|
||||
|
@ -158,7 +158,7 @@ async def test_camera_sound_event(hass):
|
|||
assert len(events) == 1
|
||||
assert events[0].data == {
|
||||
"device_id": entry.device_id,
|
||||
"type": "CameraSound",
|
||||
"type": "camera_sound",
|
||||
}
|
||||
|
||||
|
||||
|
@ -182,7 +182,7 @@ async def test_camera_person_event(hass):
|
|||
assert len(events) == 1
|
||||
assert events[0].data == {
|
||||
"device_id": entry.device_id,
|
||||
"type": "CameraPerson",
|
||||
"type": "camera_person",
|
||||
}
|
||||
|
||||
|
||||
|
@ -215,11 +215,11 @@ async def test_camera_multiple_event(hass):
|
|||
assert len(events) == 2
|
||||
assert events[0].data == {
|
||||
"device_id": entry.device_id,
|
||||
"type": "CameraMotion",
|
||||
"type": "camera_motion",
|
||||
}
|
||||
assert events[1].data == {
|
||||
"device_id": entry.device_id,
|
||||
"type": "CameraPerson",
|
||||
"type": "camera_person",
|
||||
}
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue