Reorg device automation (#26880)

* async_trigger -> async_attach_trigger

* Reorg device automations

* Update docstrings

* Fix types

* Fix extending schemas
This commit is contained in:
Paulus Schoutsen 2019-09-24 14:57:05 -07:00 committed by GitHub
parent b52cfd3409
commit 6fdff9ffab
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
48 changed files with 2014 additions and 1771 deletions

View file

@ -3,7 +3,7 @@ import asyncio
from functools import partial from functools import partial
import importlib import importlib
import logging import logging
from typing import Any from typing import Any, Awaitable, Callable
import voluptuous as vol import voluptuous as vol
@ -23,7 +23,7 @@ from homeassistant.const import (
SERVICE_TURN_ON, SERVICE_TURN_ON,
STATE_ON, STATE_ON,
) )
from homeassistant.core import Context, CoreState from homeassistant.core import Context, CoreState, HomeAssistant
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import condition, extract_domain_configs, script from homeassistant.helpers import condition, extract_domain_configs, script
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
@ -31,6 +31,7 @@ from homeassistant.helpers.config_validation import ENTITY_SERVICE_SCHEMA
from homeassistant.helpers.entity import ToggleEntity from homeassistant.helpers.entity import ToggleEntity
from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers.restore_state import RestoreEntity
from homeassistant.helpers.typing import TemplateVarsType
from homeassistant.loader import bind_hass from homeassistant.loader import bind_hass
from homeassistant.util.dt import parse_datetime, utcnow from homeassistant.util.dt import parse_datetime, utcnow
@ -67,6 +68,8 @@ SERVICE_TRIGGER = "trigger"
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
AutomationActionType = Callable[[HomeAssistant, TemplateVarsType], Awaitable[None]]
def _platform_validator(config): def _platform_validator(config):
"""Validate it is a valid platform.""" """Validate it is a valid platform."""
@ -474,7 +477,7 @@ async def _async_process_trigger(hass, config, trigger_configs, name, action):
platform = importlib.import_module(".{}".format(conf[CONF_PLATFORM]), __name__) platform = importlib.import_module(".{}".format(conf[CONF_PLATFORM]), __name__)
try: try:
remove = await platform.async_trigger(hass, conf, action, info) remove = await platform.async_attach_trigger(hass, conf, action, info)
except InvalidDeviceAutomationConfig: except InvalidDeviceAutomationConfig:
remove = False remove = False

View file

@ -13,8 +13,8 @@ TRIGGER_SCHEMA = vol.Schema(
) )
async def async_trigger(hass, config, action, automation_info): async def async_attach_trigger(hass, config, action, automation_info):
"""Listen for trigger.""" """Listen for trigger."""
integration = await async_get_integration(hass, config[CONF_DOMAIN]) integration = await async_get_integration(hass, config[CONF_DOMAIN])
platform = integration.get_platform("device_automation") platform = integration.get_platform("device_trigger")
return await platform.async_trigger(hass, config, action, automation_info) return await platform.async_attach_trigger(hass, config, action, automation_info)

View file

@ -24,7 +24,9 @@ TRIGGER_SCHEMA = vol.Schema(
) )
async def async_trigger(hass, config, action, automation_info): async def async_attach_trigger(
hass, config, action, automation_info, *, platform_type="event"
):
"""Listen for events based on configuration.""" """Listen for events based on configuration."""
event_type = config.get(CONF_EVENT_TYPE) event_type = config.get(CONF_EVENT_TYPE)
event_data_schema = ( event_data_schema = (
@ -47,7 +49,7 @@ async def async_trigger(hass, config, action, automation_info):
hass.async_run_job( hass.async_run_job(
action( action(
{"trigger": {"platform": "event", "event": event}}, {"trigger": {"platform": platform_type, "event": event}},
context=event.context, context=event.context,
) )
) )

View file

@ -37,7 +37,7 @@ def source_match(state, source):
return state and state.attributes.get("source") == source return state and state.attributes.get("source") == source
async def async_trigger(hass, config, action, automation_info): async def async_attach_trigger(hass, config, action, automation_info):
"""Listen for state changes based on configuration.""" """Listen for state changes based on configuration."""
source = config.get(CONF_SOURCE).lower() source = config.get(CONF_SOURCE).lower()
zone_entity_id = config.get(CONF_ZONE) zone_entity_id = config.get(CONF_ZONE)

View file

@ -21,7 +21,7 @@ TRIGGER_SCHEMA = vol.Schema(
) )
async def async_trigger(hass, config, action, automation_info): async def async_attach_trigger(hass, config, action, automation_info):
"""Listen for events based on configuration.""" """Listen for events based on configuration."""
event = config.get(CONF_EVENT) event = config.get(CONF_EVENT)

View file

@ -32,7 +32,7 @@ TRIGGER_SCHEMA = vol.Schema(
) )
async def async_trigger(hass, config, action, automation_info): async def async_attach_trigger(hass, config, action, automation_info):
"""Listen for events based on configuration.""" """Listen for events based on configuration."""
number = config.get(CONF_NUMBER) number = config.get(CONF_NUMBER)
held_more_than = config.get(CONF_HELD_MORE_THAN) held_more_than = config.get(CONF_HELD_MORE_THAN)

View file

@ -25,7 +25,7 @@ TRIGGER_SCHEMA = vol.Schema(
) )
async def async_trigger(hass, config, action, automation_info): async def async_attach_trigger(hass, config, action, automation_info):
"""Listen for state changes based on configuration.""" """Listen for state changes based on configuration."""
topic = config[CONF_TOPIC] topic = config[CONF_TOPIC]
payload = config.get(CONF_PAYLOAD) payload = config.get(CONF_PAYLOAD)

View file

@ -40,7 +40,7 @@ TRIGGER_SCHEMA = vol.All(
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
async def async_trigger(hass, config, action, automation_info): async def async_attach_trigger(hass, config, action, automation_info):
"""Listen for state changes based on configuration.""" """Listen for state changes based on configuration."""
entity_id = config.get(CONF_ENTITY_ID) entity_id = config.get(CONF_ENTITY_ID)
below = config.get(CONF_BELOW) below = config.get(CONF_BELOW)

View file

@ -37,7 +37,9 @@ TRIGGER_SCHEMA = vol.All(
) )
async def async_trigger(hass, config, action, automation_info): async def async_attach_trigger(
hass, config, action, automation_info, *, platform_type="state"
):
"""Listen for state changes based on configuration.""" """Listen for state changes based on configuration."""
entity_id = config.get(CONF_ENTITY_ID) entity_id = config.get(CONF_ENTITY_ID)
from_state = config.get(CONF_FROM, MATCH_ALL) from_state = config.get(CONF_FROM, MATCH_ALL)
@ -59,7 +61,7 @@ async def async_trigger(hass, config, action, automation_info):
action( action(
{ {
"trigger": { "trigger": {
"platform": "state", "platform": platform_type,
"entity_id": entity, "entity_id": entity,
"from_state": from_s, "from_state": from_s,
"to_state": to_s, "to_state": to_s,

View file

@ -28,7 +28,7 @@ TRIGGER_SCHEMA = vol.Schema(
) )
async def async_trigger(hass, config, action, automation_info): async def async_attach_trigger(hass, config, action, automation_info):
"""Listen for events based on configuration.""" """Listen for events based on configuration."""
event = config.get(CONF_EVENT) event = config.get(CONF_EVENT)
offset = config.get(CONF_OFFSET) offset = config.get(CONF_OFFSET)

View file

@ -28,7 +28,7 @@ TRIGGER_SCHEMA = IF_ACTION_SCHEMA = vol.Schema(
) )
async def async_trigger(hass, config, action, automation_info): async def async_attach_trigger(hass, config, action, automation_info):
"""Listen for state changes based on configuration.""" """Listen for state changes based on configuration."""
value_template = config.get(CONF_VALUE_TEMPLATE) value_template = config.get(CONF_VALUE_TEMPLATE)
value_template.hass = hass value_template.hass = hass

View file

@ -18,7 +18,7 @@ TRIGGER_SCHEMA = vol.Schema(
) )
async def async_trigger(hass, config, action, automation_info): async def async_attach_trigger(hass, config, action, automation_info):
"""Listen for state changes based on configuration.""" """Listen for state changes based on configuration."""
at_time = config.get(CONF_AT) at_time = config.get(CONF_AT)
hours, minutes, seconds = at_time.hour, at_time.minute, at_time.second hours, minutes, seconds = at_time.hour, at_time.minute, at_time.second

View file

@ -30,7 +30,7 @@ TRIGGER_SCHEMA = vol.All(
) )
async def async_trigger(hass, config, action, automation_info): async def async_attach_trigger(hass, config, action, automation_info):
"""Listen for state changes based on configuration.""" """Listen for state changes based on configuration."""
hours = config.get(CONF_HOURS) hours = config.get(CONF_HOURS)
minutes = config.get(CONF_MINUTES) minutes = config.get(CONF_MINUTES)

View file

@ -36,7 +36,7 @@ async def _handle_webhook(action, hass, webhook_id, request):
hass.async_run_job(action, {"trigger": result}) hass.async_run_job(action, {"trigger": result})
async def async_trigger(hass, config, action, automation_info): async def async_attach_trigger(hass, config, action, automation_info):
"""Trigger based on incoming webhooks.""" """Trigger based on incoming webhooks."""
webhook_id = config.get(CONF_WEBHOOK_ID) webhook_id = config.get(CONF_WEBHOOK_ID)
hass.components.webhook.async_register( hass.components.webhook.async_register(

View file

@ -31,7 +31,7 @@ TRIGGER_SCHEMA = vol.Schema(
) )
async def async_trigger(hass, config, action, automation_info): async def async_attach_trigger(hass, config, action, automation_info):
"""Listen for state changes based on configuration.""" """Listen for state changes based on configuration."""
entity_id = config.get(CONF_ENTITY_ID) entity_id = config.get(CONF_ENTITY_ID)
zone_entity_id = config.get(CONF_ZONE) zone_entity_id = config.get(CONF_ZONE)

View file

@ -1,423 +0,0 @@
"""Provides device automations for lights."""
import voluptuous as vol
import homeassistant.components.automation.state as state
from homeassistant.components.device_automation.const import (
CONF_IS_OFF,
CONF_IS_ON,
CONF_TURNED_OFF,
CONF_TURNED_ON,
)
from homeassistant.const import (
ATTR_DEVICE_CLASS,
CONF_CONDITION,
CONF_DEVICE_ID,
CONF_DOMAIN,
CONF_ENTITY_ID,
CONF_PLATFORM,
CONF_TYPE,
)
from homeassistant.core import split_entity_id
from homeassistant.helpers.entity_registry import async_entries_for_device
from homeassistant.helpers import condition, config_validation as cv
from . import (
DOMAIN,
DEVICE_CLASS_BATTERY,
DEVICE_CLASS_COLD,
DEVICE_CLASS_CONNECTIVITY,
DEVICE_CLASS_DOOR,
DEVICE_CLASS_GARAGE_DOOR,
DEVICE_CLASS_GAS,
DEVICE_CLASS_HEAT,
DEVICE_CLASS_LIGHT,
DEVICE_CLASS_LOCK,
DEVICE_CLASS_MOISTURE,
DEVICE_CLASS_MOTION,
DEVICE_CLASS_MOVING,
DEVICE_CLASS_OCCUPANCY,
DEVICE_CLASS_OPENING,
DEVICE_CLASS_PLUG,
DEVICE_CLASS_POWER,
DEVICE_CLASS_PRESENCE,
DEVICE_CLASS_PROBLEM,
DEVICE_CLASS_SAFETY,
DEVICE_CLASS_SMOKE,
DEVICE_CLASS_SOUND,
DEVICE_CLASS_VIBRATION,
DEVICE_CLASS_WINDOW,
)
# mypy: allow-untyped-defs, no-check-untyped-defs
DEVICE_CLASS_NONE = "none"
CONF_IS_BAT_LOW = "is_bat_low"
CONF_IS_NOT_BAT_LOW = "is_not_bat_low"
CONF_IS_COLD = "is_cold"
CONF_IS_NOT_COLD = "is_not_cold"
CONF_IS_CONNECTED = "is_connected"
CONF_IS_NOT_CONNECTED = "is_not_connected"
CONF_IS_GAS = "is_gas"
CONF_IS_NO_GAS = "is_no_gas"
CONF_IS_HOT = "is_hot"
CONF_IS_NOT_HOT = "is_not_hot"
CONF_IS_LIGHT = "is_light"
CONF_IS_NO_LIGHT = "is_no_light"
CONF_IS_LOCKED = "is_locked"
CONF_IS_NOT_LOCKED = "is_not_locked"
CONF_IS_MOIST = "is_moist"
CONF_IS_NOT_MOIST = "is_not_moist"
CONF_IS_MOTION = "is_motion"
CONF_IS_NO_MOTION = "is_no_motion"
CONF_IS_MOVING = "is_moving"
CONF_IS_NOT_MOVING = "is_not_moving"
CONF_IS_OCCUPIED = "is_occupied"
CONF_IS_NOT_OCCUPIED = "is_not_occupied"
CONF_IS_PLUGGED_IN = "is_plugged_in"
CONF_IS_NOT_PLUGGED_IN = "is_not_plugged_in"
CONF_IS_POWERED = "is_powered"
CONF_IS_NOT_POWERED = "is_not_powered"
CONF_IS_PRESENT = "is_present"
CONF_IS_NOT_PRESENT = "is_not_present"
CONF_IS_PROBLEM = "is_problem"
CONF_IS_NO_PROBLEM = "is_no_problem"
CONF_IS_UNSAFE = "is_unsafe"
CONF_IS_NOT_UNSAFE = "is_not_unsafe"
CONF_IS_SMOKE = "is_smoke"
CONF_IS_NO_SMOKE = "is_no_smoke"
CONF_IS_SOUND = "is_sound"
CONF_IS_NO_SOUND = "is_no_sound"
CONF_IS_VIBRATION = "is_vibration"
CONF_IS_NO_VIBRATION = "is_no_vibration"
CONF_IS_OPEN = "is_open"
CONF_IS_NOT_OPEN = "is_not_open"
CONF_BAT_LOW = "bat_low"
CONF_NOT_BAT_LOW = "not_bat_low"
CONF_COLD = "cold"
CONF_NOT_COLD = "not_cold"
CONF_CONNECTED = "connected"
CONF_NOT_CONNECTED = "not_connected"
CONF_GAS = "gas"
CONF_NO_GAS = "no_gas"
CONF_HOT = "hot"
CONF_NOT_HOT = "not_hot"
CONF_LIGHT = "light"
CONF_NO_LIGHT = "no_light"
CONF_LOCKED = "locked"
CONF_NOT_LOCKED = "not_locked"
CONF_MOIST = "moist"
CONF_NOT_MOIST = "not_moist"
CONF_MOTION = "motion"
CONF_NO_MOTION = "no_motion"
CONF_MOVING = "moving"
CONF_NOT_MOVING = "not_moving"
CONF_OCCUPIED = "occupied"
CONF_NOT_OCCUPIED = "not_occupied"
CONF_PLUGGED_IN = "plugged_in"
CONF_NOT_PLUGGED_IN = "not_plugged_in"
CONF_POWERED = "powered"
CONF_NOT_POWERED = "not_powered"
CONF_PRESENT = "present"
CONF_NOT_PRESENT = "not_present"
CONF_PROBLEM = "problem"
CONF_NO_PROBLEM = "no_problem"
CONF_UNSAFE = "unsafe"
CONF_NOT_UNSAFE = "not_unsafe"
CONF_SMOKE = "smoke"
CONF_NO_SMOKE = "no_smoke"
CONF_SOUND = "sound"
CONF_NO_SOUND = "no_sound"
CONF_VIBRATION = "vibration"
CONF_NO_VIBRATION = "no_vibration"
CONF_OPEN = "open"
CONF_NOT_OPEN = "not_open"
IS_ON = [
CONF_IS_BAT_LOW,
CONF_IS_COLD,
CONF_IS_CONNECTED,
CONF_IS_GAS,
CONF_IS_HOT,
CONF_IS_LIGHT,
CONF_IS_LOCKED,
CONF_IS_MOIST,
CONF_IS_MOTION,
CONF_IS_MOVING,
CONF_IS_OCCUPIED,
CONF_IS_OPEN,
CONF_IS_PLUGGED_IN,
CONF_IS_POWERED,
CONF_IS_PRESENT,
CONF_IS_PROBLEM,
CONF_IS_SMOKE,
CONF_IS_SOUND,
CONF_IS_UNSAFE,
CONF_IS_VIBRATION,
CONF_IS_ON,
]
IS_OFF = [
CONF_IS_NOT_BAT_LOW,
CONF_IS_NOT_COLD,
CONF_IS_NOT_CONNECTED,
CONF_IS_NOT_HOT,
CONF_IS_NOT_LOCKED,
CONF_IS_NOT_MOIST,
CONF_IS_NOT_MOVING,
CONF_IS_NOT_OCCUPIED,
CONF_IS_NOT_OPEN,
CONF_IS_NOT_PLUGGED_IN,
CONF_IS_NOT_POWERED,
CONF_IS_NOT_PRESENT,
CONF_IS_NOT_UNSAFE,
CONF_IS_NO_GAS,
CONF_IS_NO_LIGHT,
CONF_IS_NO_MOTION,
CONF_IS_NO_PROBLEM,
CONF_IS_NO_SMOKE,
CONF_IS_NO_SOUND,
CONF_IS_NO_VIBRATION,
CONF_IS_OFF,
]
TURNED_ON = [
CONF_BAT_LOW,
CONF_COLD,
CONF_CONNECTED,
CONF_GAS,
CONF_HOT,
CONF_LIGHT,
CONF_LOCKED,
CONF_MOIST,
CONF_MOTION,
CONF_MOVING,
CONF_OCCUPIED,
CONF_OPEN,
CONF_PLUGGED_IN,
CONF_POWERED,
CONF_PRESENT,
CONF_PROBLEM,
CONF_SMOKE,
CONF_SOUND,
CONF_UNSAFE,
CONF_VIBRATION,
CONF_TURNED_ON,
]
TURNED_OFF = [
CONF_NOT_BAT_LOW,
CONF_NOT_COLD,
CONF_NOT_CONNECTED,
CONF_NOT_HOT,
CONF_NOT_LOCKED,
CONF_NOT_MOIST,
CONF_NOT_MOVING,
CONF_NOT_OCCUPIED,
CONF_NOT_OPEN,
CONF_NOT_PLUGGED_IN,
CONF_NOT_POWERED,
CONF_NOT_PRESENT,
CONF_NOT_UNSAFE,
CONF_NO_GAS,
CONF_NO_LIGHT,
CONF_NO_MOTION,
CONF_NO_PROBLEM,
CONF_NO_SMOKE,
CONF_NO_SOUND,
CONF_NO_VIBRATION,
CONF_TURNED_OFF,
]
ENTITY_CONDITIONS = {
DEVICE_CLASS_BATTERY: [
{CONF_TYPE: CONF_IS_BAT_LOW},
{CONF_TYPE: CONF_IS_NOT_BAT_LOW},
],
DEVICE_CLASS_COLD: [{CONF_TYPE: CONF_IS_COLD}, {CONF_TYPE: CONF_IS_NOT_COLD}],
DEVICE_CLASS_CONNECTIVITY: [
{CONF_TYPE: CONF_IS_CONNECTED},
{CONF_TYPE: CONF_IS_NOT_CONNECTED},
],
DEVICE_CLASS_DOOR: [{CONF_TYPE: CONF_IS_OPEN}, {CONF_TYPE: CONF_IS_NOT_OPEN}],
DEVICE_CLASS_GARAGE_DOOR: [
{CONF_TYPE: CONF_IS_OPEN},
{CONF_TYPE: CONF_IS_NOT_OPEN},
],
DEVICE_CLASS_GAS: [{CONF_TYPE: CONF_IS_GAS}, {CONF_TYPE: CONF_IS_NO_GAS}],
DEVICE_CLASS_HEAT: [{CONF_TYPE: CONF_IS_HOT}, {CONF_TYPE: CONF_IS_NOT_HOT}],
DEVICE_CLASS_LIGHT: [{CONF_TYPE: CONF_IS_LIGHT}, {CONF_TYPE: CONF_IS_NO_LIGHT}],
DEVICE_CLASS_LOCK: [{CONF_TYPE: CONF_IS_LOCKED}, {CONF_TYPE: CONF_IS_NOT_LOCKED}],
DEVICE_CLASS_MOISTURE: [{CONF_TYPE: CONF_IS_MOIST}, {CONF_TYPE: CONF_IS_NOT_MOIST}],
DEVICE_CLASS_MOTION: [{CONF_TYPE: CONF_IS_MOTION}, {CONF_TYPE: CONF_IS_NO_MOTION}],
DEVICE_CLASS_MOVING: [{CONF_TYPE: CONF_IS_MOVING}, {CONF_TYPE: CONF_IS_NOT_MOVING}],
DEVICE_CLASS_OCCUPANCY: [
{CONF_TYPE: CONF_IS_OCCUPIED},
{CONF_TYPE: CONF_IS_NOT_OCCUPIED},
],
DEVICE_CLASS_OPENING: [{CONF_TYPE: CONF_IS_OPEN}, {CONF_TYPE: CONF_IS_NOT_OPEN}],
DEVICE_CLASS_PLUG: [
{CONF_TYPE: CONF_IS_PLUGGED_IN},
{CONF_TYPE: CONF_IS_NOT_PLUGGED_IN},
],
DEVICE_CLASS_POWER: [
{CONF_TYPE: CONF_IS_POWERED},
{CONF_TYPE: CONF_IS_NOT_POWERED},
],
DEVICE_CLASS_PRESENCE: [
{CONF_TYPE: CONF_IS_PRESENT},
{CONF_TYPE: CONF_IS_NOT_PRESENT},
],
DEVICE_CLASS_PROBLEM: [
{CONF_TYPE: CONF_IS_PROBLEM},
{CONF_TYPE: CONF_IS_NO_PROBLEM},
],
DEVICE_CLASS_SAFETY: [{CONF_TYPE: CONF_IS_UNSAFE}, {CONF_TYPE: CONF_IS_NOT_UNSAFE}],
DEVICE_CLASS_SMOKE: [{CONF_TYPE: CONF_IS_SMOKE}, {CONF_TYPE: CONF_IS_NO_SMOKE}],
DEVICE_CLASS_SOUND: [{CONF_TYPE: CONF_IS_SOUND}, {CONF_TYPE: CONF_IS_NO_SOUND}],
DEVICE_CLASS_VIBRATION: [
{CONF_TYPE: CONF_IS_VIBRATION},
{CONF_TYPE: CONF_IS_NO_VIBRATION},
],
DEVICE_CLASS_WINDOW: [{CONF_TYPE: CONF_IS_OPEN}, {CONF_TYPE: CONF_IS_NOT_OPEN}],
DEVICE_CLASS_NONE: [{CONF_TYPE: CONF_IS_ON}, {CONF_TYPE: CONF_IS_OFF}],
}
ENTITY_TRIGGERS = {
DEVICE_CLASS_BATTERY: [{CONF_TYPE: CONF_BAT_LOW}, {CONF_TYPE: CONF_NOT_BAT_LOW}],
DEVICE_CLASS_COLD: [{CONF_TYPE: CONF_COLD}, {CONF_TYPE: CONF_NOT_COLD}],
DEVICE_CLASS_CONNECTIVITY: [
{CONF_TYPE: CONF_CONNECTED},
{CONF_TYPE: CONF_NOT_CONNECTED},
],
DEVICE_CLASS_DOOR: [{CONF_TYPE: CONF_OPEN}, {CONF_TYPE: CONF_NOT_OPEN}],
DEVICE_CLASS_GARAGE_DOOR: [{CONF_TYPE: CONF_OPEN}, {CONF_TYPE: CONF_NOT_OPEN}],
DEVICE_CLASS_GAS: [{CONF_TYPE: CONF_GAS}, {CONF_TYPE: CONF_NO_GAS}],
DEVICE_CLASS_HEAT: [{CONF_TYPE: CONF_HOT}, {CONF_TYPE: CONF_NOT_HOT}],
DEVICE_CLASS_LIGHT: [{CONF_TYPE: CONF_LIGHT}, {CONF_TYPE: CONF_NO_LIGHT}],
DEVICE_CLASS_LOCK: [{CONF_TYPE: CONF_LOCKED}, {CONF_TYPE: CONF_NOT_LOCKED}],
DEVICE_CLASS_MOISTURE: [{CONF_TYPE: CONF_MOIST}, {CONF_TYPE: CONF_NOT_MOIST}],
DEVICE_CLASS_MOTION: [{CONF_TYPE: CONF_MOTION}, {CONF_TYPE: CONF_NO_MOTION}],
DEVICE_CLASS_MOVING: [{CONF_TYPE: CONF_MOVING}, {CONF_TYPE: CONF_NOT_MOVING}],
DEVICE_CLASS_OCCUPANCY: [
{CONF_TYPE: CONF_OCCUPIED},
{CONF_TYPE: CONF_NOT_OCCUPIED},
],
DEVICE_CLASS_OPENING: [{CONF_TYPE: CONF_OPEN}, {CONF_TYPE: CONF_NOT_OPEN}],
DEVICE_CLASS_PLUG: [{CONF_TYPE: CONF_PLUGGED_IN}, {CONF_TYPE: CONF_NOT_PLUGGED_IN}],
DEVICE_CLASS_POWER: [{CONF_TYPE: CONF_POWERED}, {CONF_TYPE: CONF_NOT_POWERED}],
DEVICE_CLASS_PRESENCE: [{CONF_TYPE: CONF_PRESENT}, {CONF_TYPE: CONF_NOT_PRESENT}],
DEVICE_CLASS_PROBLEM: [{CONF_TYPE: CONF_PROBLEM}, {CONF_TYPE: CONF_NO_PROBLEM}],
DEVICE_CLASS_SAFETY: [{CONF_TYPE: CONF_UNSAFE}, {CONF_TYPE: CONF_NOT_UNSAFE}],
DEVICE_CLASS_SMOKE: [{CONF_TYPE: CONF_SMOKE}, {CONF_TYPE: CONF_NO_SMOKE}],
DEVICE_CLASS_SOUND: [{CONF_TYPE: CONF_SOUND}, {CONF_TYPE: CONF_NO_SOUND}],
DEVICE_CLASS_VIBRATION: [
{CONF_TYPE: CONF_VIBRATION},
{CONF_TYPE: CONF_NO_VIBRATION},
],
DEVICE_CLASS_WINDOW: [{CONF_TYPE: CONF_OPEN}, {CONF_TYPE: CONF_NOT_OPEN}],
DEVICE_CLASS_NONE: [{CONF_TYPE: CONF_TURNED_ON}, {CONF_TYPE: CONF_TURNED_OFF}],
}
CONDITION_SCHEMA = vol.Schema(
{
vol.Required(CONF_CONDITION): "device",
vol.Required(CONF_DEVICE_ID): str,
vol.Required(CONF_DOMAIN): DOMAIN,
vol.Required(CONF_ENTITY_ID): cv.entity_id,
vol.Required(CONF_TYPE): vol.In(IS_OFF + IS_ON),
}
)
TRIGGER_SCHEMA = vol.Schema(
{
vol.Required(CONF_PLATFORM): "device",
vol.Required(CONF_DEVICE_ID): str,
vol.Required(CONF_DOMAIN): DOMAIN,
vol.Required(CONF_ENTITY_ID): cv.entity_id,
vol.Required(CONF_TYPE): vol.In(TURNED_OFF + TURNED_ON),
}
)
def async_condition_from_config(config, config_validation):
"""Evaluate state based on configuration."""
config = CONDITION_SCHEMA(config)
condition_type = config[CONF_TYPE]
if condition_type in IS_ON:
stat = "on"
else:
stat = "off"
state_config = {
condition.CONF_CONDITION: "state",
condition.CONF_ENTITY_ID: config[CONF_ENTITY_ID],
condition.CONF_STATE: stat,
}
return condition.state_from_config(state_config, config_validation)
async def async_trigger(hass, config, action, automation_info):
"""Listen for state changes based on configuration."""
config = TRIGGER_SCHEMA(config)
trigger_type = config[CONF_TYPE]
if trigger_type in TURNED_ON:
from_state = "off"
to_state = "on"
else:
from_state = "on"
to_state = "off"
state_config = {
state.CONF_ENTITY_ID: config[CONF_ENTITY_ID],
state.CONF_FROM: from_state,
state.CONF_TO: to_state,
}
return await state.async_trigger(hass, state_config, action, automation_info)
def _is_domain(entity, domain):
return split_entity_id(entity.entity_id)[0] == domain
async def _async_get_automations(hass, device_id, automation_templates, domain):
"""List device automations."""
automations = []
entity_registry = await hass.helpers.entity_registry.async_get_registry()
entities = async_entries_for_device(entity_registry, device_id)
domain_entities = [x for x in entities if _is_domain(x, domain)]
for entity in domain_entities:
device_class = DEVICE_CLASS_NONE
entity_id = entity.entity_id
entity = hass.states.get(entity_id)
if entity and ATTR_DEVICE_CLASS in entity.attributes:
device_class = entity.attributes[ATTR_DEVICE_CLASS]
automation_template = automation_templates[device_class]
for automation in automation_template:
automation = dict(automation)
automation.update(device_id=device_id, entity_id=entity_id, domain=domain)
automations.append(automation)
return automations
async def async_get_conditions(hass, device_id):
"""List device conditions."""
automations = await _async_get_automations(
hass, device_id, ENTITY_CONDITIONS, DOMAIN
)
for automation in automations:
automation.update(condition="device")
return automations
async def async_get_triggers(hass, device_id):
"""List device triggers."""
automations = await _async_get_automations(hass, device_id, ENTITY_TRIGGERS, DOMAIN)
for automation in automations:
automation.update(platform="device")
return automations

View file

@ -0,0 +1,247 @@
"""Implemenet device conditions for binary sensor."""
from typing import List
import voluptuous as vol
from homeassistant.core import HomeAssistant
from homeassistant.components.device_automation.const import CONF_IS_OFF, CONF_IS_ON
from homeassistant.const import ATTR_DEVICE_CLASS, CONF_ENTITY_ID, CONF_TYPE
from homeassistant.helpers import condition, config_validation as cv
from homeassistant.helpers.entity_registry import (
async_entries_for_device,
async_get_registry,
)
from homeassistant.helpers.typing import ConfigType
from . import (
DOMAIN,
DEVICE_CLASS_BATTERY,
DEVICE_CLASS_COLD,
DEVICE_CLASS_CONNECTIVITY,
DEVICE_CLASS_DOOR,
DEVICE_CLASS_GARAGE_DOOR,
DEVICE_CLASS_GAS,
DEVICE_CLASS_HEAT,
DEVICE_CLASS_LIGHT,
DEVICE_CLASS_LOCK,
DEVICE_CLASS_MOISTURE,
DEVICE_CLASS_MOTION,
DEVICE_CLASS_MOVING,
DEVICE_CLASS_OCCUPANCY,
DEVICE_CLASS_OPENING,
DEVICE_CLASS_PLUG,
DEVICE_CLASS_POWER,
DEVICE_CLASS_PRESENCE,
DEVICE_CLASS_PROBLEM,
DEVICE_CLASS_SAFETY,
DEVICE_CLASS_SMOKE,
DEVICE_CLASS_SOUND,
DEVICE_CLASS_VIBRATION,
DEVICE_CLASS_WINDOW,
)
DEVICE_CLASS_NONE = "none"
CONF_IS_BAT_LOW = "is_bat_low"
CONF_IS_NOT_BAT_LOW = "is_not_bat_low"
CONF_IS_COLD = "is_cold"
CONF_IS_NOT_COLD = "is_not_cold"
CONF_IS_CONNECTED = "is_connected"
CONF_IS_NOT_CONNECTED = "is_not_connected"
CONF_IS_GAS = "is_gas"
CONF_IS_NO_GAS = "is_no_gas"
CONF_IS_HOT = "is_hot"
CONF_IS_NOT_HOT = "is_not_hot"
CONF_IS_LIGHT = "is_light"
CONF_IS_NO_LIGHT = "is_no_light"
CONF_IS_LOCKED = "is_locked"
CONF_IS_NOT_LOCKED = "is_not_locked"
CONF_IS_MOIST = "is_moist"
CONF_IS_NOT_MOIST = "is_not_moist"
CONF_IS_MOTION = "is_motion"
CONF_IS_NO_MOTION = "is_no_motion"
CONF_IS_MOVING = "is_moving"
CONF_IS_NOT_MOVING = "is_not_moving"
CONF_IS_OCCUPIED = "is_occupied"
CONF_IS_NOT_OCCUPIED = "is_not_occupied"
CONF_IS_PLUGGED_IN = "is_plugged_in"
CONF_IS_NOT_PLUGGED_IN = "is_not_plugged_in"
CONF_IS_POWERED = "is_powered"
CONF_IS_NOT_POWERED = "is_not_powered"
CONF_IS_PRESENT = "is_present"
CONF_IS_NOT_PRESENT = "is_not_present"
CONF_IS_PROBLEM = "is_problem"
CONF_IS_NO_PROBLEM = "is_no_problem"
CONF_IS_UNSAFE = "is_unsafe"
CONF_IS_NOT_UNSAFE = "is_not_unsafe"
CONF_IS_SMOKE = "is_smoke"
CONF_IS_NO_SMOKE = "is_no_smoke"
CONF_IS_SOUND = "is_sound"
CONF_IS_NO_SOUND = "is_no_sound"
CONF_IS_VIBRATION = "is_vibration"
CONF_IS_NO_VIBRATION = "is_no_vibration"
CONF_IS_OPEN = "is_open"
CONF_IS_NOT_OPEN = "is_not_open"
IS_ON = [
CONF_IS_BAT_LOW,
CONF_IS_COLD,
CONF_IS_CONNECTED,
CONF_IS_GAS,
CONF_IS_HOT,
CONF_IS_LIGHT,
CONF_IS_LOCKED,
CONF_IS_MOIST,
CONF_IS_MOTION,
CONF_IS_MOVING,
CONF_IS_OCCUPIED,
CONF_IS_OPEN,
CONF_IS_PLUGGED_IN,
CONF_IS_POWERED,
CONF_IS_PRESENT,
CONF_IS_PROBLEM,
CONF_IS_SMOKE,
CONF_IS_SOUND,
CONF_IS_UNSAFE,
CONF_IS_VIBRATION,
CONF_IS_ON,
]
IS_OFF = [
CONF_IS_NOT_BAT_LOW,
CONF_IS_NOT_COLD,
CONF_IS_NOT_CONNECTED,
CONF_IS_NOT_HOT,
CONF_IS_NOT_LOCKED,
CONF_IS_NOT_MOIST,
CONF_IS_NOT_MOVING,
CONF_IS_NOT_OCCUPIED,
CONF_IS_NOT_OPEN,
CONF_IS_NOT_PLUGGED_IN,
CONF_IS_NOT_POWERED,
CONF_IS_NOT_PRESENT,
CONF_IS_NOT_UNSAFE,
CONF_IS_NO_GAS,
CONF_IS_NO_LIGHT,
CONF_IS_NO_MOTION,
CONF_IS_NO_PROBLEM,
CONF_IS_NO_SMOKE,
CONF_IS_NO_SOUND,
CONF_IS_NO_VIBRATION,
CONF_IS_OFF,
]
ENTITY_CONDITIONS = {
DEVICE_CLASS_BATTERY: [
{CONF_TYPE: CONF_IS_BAT_LOW},
{CONF_TYPE: CONF_IS_NOT_BAT_LOW},
],
DEVICE_CLASS_COLD: [{CONF_TYPE: CONF_IS_COLD}, {CONF_TYPE: CONF_IS_NOT_COLD}],
DEVICE_CLASS_CONNECTIVITY: [
{CONF_TYPE: CONF_IS_CONNECTED},
{CONF_TYPE: CONF_IS_NOT_CONNECTED},
],
DEVICE_CLASS_DOOR: [{CONF_TYPE: CONF_IS_OPEN}, {CONF_TYPE: CONF_IS_NOT_OPEN}],
DEVICE_CLASS_GARAGE_DOOR: [
{CONF_TYPE: CONF_IS_OPEN},
{CONF_TYPE: CONF_IS_NOT_OPEN},
],
DEVICE_CLASS_GAS: [{CONF_TYPE: CONF_IS_GAS}, {CONF_TYPE: CONF_IS_NO_GAS}],
DEVICE_CLASS_HEAT: [{CONF_TYPE: CONF_IS_HOT}, {CONF_TYPE: CONF_IS_NOT_HOT}],
DEVICE_CLASS_LIGHT: [{CONF_TYPE: CONF_IS_LIGHT}, {CONF_TYPE: CONF_IS_NO_LIGHT}],
DEVICE_CLASS_LOCK: [{CONF_TYPE: CONF_IS_LOCKED}, {CONF_TYPE: CONF_IS_NOT_LOCKED}],
DEVICE_CLASS_MOISTURE: [{CONF_TYPE: CONF_IS_MOIST}, {CONF_TYPE: CONF_IS_NOT_MOIST}],
DEVICE_CLASS_MOTION: [{CONF_TYPE: CONF_IS_MOTION}, {CONF_TYPE: CONF_IS_NO_MOTION}],
DEVICE_CLASS_MOVING: [{CONF_TYPE: CONF_IS_MOVING}, {CONF_TYPE: CONF_IS_NOT_MOVING}],
DEVICE_CLASS_OCCUPANCY: [
{CONF_TYPE: CONF_IS_OCCUPIED},
{CONF_TYPE: CONF_IS_NOT_OCCUPIED},
],
DEVICE_CLASS_OPENING: [{CONF_TYPE: CONF_IS_OPEN}, {CONF_TYPE: CONF_IS_NOT_OPEN}],
DEVICE_CLASS_PLUG: [
{CONF_TYPE: CONF_IS_PLUGGED_IN},
{CONF_TYPE: CONF_IS_NOT_PLUGGED_IN},
],
DEVICE_CLASS_POWER: [
{CONF_TYPE: CONF_IS_POWERED},
{CONF_TYPE: CONF_IS_NOT_POWERED},
],
DEVICE_CLASS_PRESENCE: [
{CONF_TYPE: CONF_IS_PRESENT},
{CONF_TYPE: CONF_IS_NOT_PRESENT},
],
DEVICE_CLASS_PROBLEM: [
{CONF_TYPE: CONF_IS_PROBLEM},
{CONF_TYPE: CONF_IS_NO_PROBLEM},
],
DEVICE_CLASS_SAFETY: [{CONF_TYPE: CONF_IS_UNSAFE}, {CONF_TYPE: CONF_IS_NOT_UNSAFE}],
DEVICE_CLASS_SMOKE: [{CONF_TYPE: CONF_IS_SMOKE}, {CONF_TYPE: CONF_IS_NO_SMOKE}],
DEVICE_CLASS_SOUND: [{CONF_TYPE: CONF_IS_SOUND}, {CONF_TYPE: CONF_IS_NO_SOUND}],
DEVICE_CLASS_VIBRATION: [
{CONF_TYPE: CONF_IS_VIBRATION},
{CONF_TYPE: CONF_IS_NO_VIBRATION},
],
DEVICE_CLASS_WINDOW: [{CONF_TYPE: CONF_IS_OPEN}, {CONF_TYPE: CONF_IS_NOT_OPEN}],
DEVICE_CLASS_NONE: [{CONF_TYPE: CONF_IS_ON}, {CONF_TYPE: CONF_IS_OFF}],
}
CONDITION_SCHEMA = cv.DEVICE_CONDITION_BASE_SCHEMA.extend(
{
vol.Required(CONF_ENTITY_ID): cv.entity_id,
vol.Required(CONF_TYPE): vol.In(IS_OFF + IS_ON),
}
)
async def async_get_conditions(hass: HomeAssistant, device_id: str) -> List[dict]:
"""List device conditions."""
conditions: List[dict] = []
entity_registry = await async_get_registry(hass)
entries = [
entry
for entry in async_entries_for_device(entity_registry, device_id)
if entry.domain == DOMAIN
]
for entry in entries:
device_class = DEVICE_CLASS_NONE
state = hass.states.get(entry.entity_id)
if state and ATTR_DEVICE_CLASS in state.attributes:
device_class = state.attributes[ATTR_DEVICE_CLASS]
templates = ENTITY_CONDITIONS.get(
device_class, ENTITY_CONDITIONS[DEVICE_CLASS_NONE]
)
conditions.extend(
(
{
**template,
"condition": "device",
"device_id": device_id,
"entity_id": entry.entity_id,
"domain": DOMAIN,
}
for template in templates
)
)
return conditions
def async_condition_from_config(
config: ConfigType, config_validation: bool
) -> condition.ConditionCheckerType:
"""Evaluate state based on configuration."""
config = CONDITION_SCHEMA(config)
condition_type = config[CONF_TYPE]
if condition_type in IS_ON:
stat = "on"
else:
stat = "off"
state_config = {
condition.CONF_CONDITION: "state",
condition.CONF_ENTITY_ID: config[CONF_ENTITY_ID],
condition.CONF_STATE: stat,
}
return condition.state_from_config(state_config, config_validation)

View file

@ -0,0 +1,238 @@
"""Provides device triggers for binary sensors."""
import voluptuous as vol
from homeassistant.components.automation import state as state_automation
from homeassistant.components.device_automation import TRIGGER_BASE_SCHEMA
from homeassistant.components.device_automation.const import (
CONF_TURNED_OFF,
CONF_TURNED_ON,
)
from homeassistant.const import ATTR_DEVICE_CLASS, CONF_ENTITY_ID, CONF_TYPE
from homeassistant.helpers.entity_registry import async_entries_for_device
from homeassistant.helpers import config_validation as cv
from . import (
DOMAIN,
DEVICE_CLASS_BATTERY,
DEVICE_CLASS_COLD,
DEVICE_CLASS_CONNECTIVITY,
DEVICE_CLASS_DOOR,
DEVICE_CLASS_GARAGE_DOOR,
DEVICE_CLASS_GAS,
DEVICE_CLASS_HEAT,
DEVICE_CLASS_LIGHT,
DEVICE_CLASS_LOCK,
DEVICE_CLASS_MOISTURE,
DEVICE_CLASS_MOTION,
DEVICE_CLASS_MOVING,
DEVICE_CLASS_OCCUPANCY,
DEVICE_CLASS_OPENING,
DEVICE_CLASS_PLUG,
DEVICE_CLASS_POWER,
DEVICE_CLASS_PRESENCE,
DEVICE_CLASS_PROBLEM,
DEVICE_CLASS_SAFETY,
DEVICE_CLASS_SMOKE,
DEVICE_CLASS_SOUND,
DEVICE_CLASS_VIBRATION,
DEVICE_CLASS_WINDOW,
)
# mypy: allow-untyped-defs, no-check-untyped-defs
DEVICE_CLASS_NONE = "none"
CONF_BAT_LOW = "bat_low"
CONF_NOT_BAT_LOW = "not_bat_low"
CONF_COLD = "cold"
CONF_NOT_COLD = "not_cold"
CONF_CONNECTED = "connected"
CONF_NOT_CONNECTED = "not_connected"
CONF_GAS = "gas"
CONF_NO_GAS = "no_gas"
CONF_HOT = "hot"
CONF_NOT_HOT = "not_hot"
CONF_LIGHT = "light"
CONF_NO_LIGHT = "no_light"
CONF_LOCKED = "locked"
CONF_NOT_LOCKED = "not_locked"
CONF_MOIST = "moist"
CONF_NOT_MOIST = "not_moist"
CONF_MOTION = "motion"
CONF_NO_MOTION = "no_motion"
CONF_MOVING = "moving"
CONF_NOT_MOVING = "not_moving"
CONF_OCCUPIED = "occupied"
CONF_NOT_OCCUPIED = "not_occupied"
CONF_PLUGGED_IN = "plugged_in"
CONF_NOT_PLUGGED_IN = "not_plugged_in"
CONF_POWERED = "powered"
CONF_NOT_POWERED = "not_powered"
CONF_PRESENT = "present"
CONF_NOT_PRESENT = "not_present"
CONF_PROBLEM = "problem"
CONF_NO_PROBLEM = "no_problem"
CONF_UNSAFE = "unsafe"
CONF_NOT_UNSAFE = "not_unsafe"
CONF_SMOKE = "smoke"
CONF_NO_SMOKE = "no_smoke"
CONF_SOUND = "sound"
CONF_NO_SOUND = "no_sound"
CONF_VIBRATION = "vibration"
CONF_NO_VIBRATION = "no_vibration"
CONF_OPEN = "open"
CONF_NOT_OPEN = "not_open"
TURNED_ON = [
CONF_BAT_LOW,
CONF_COLD,
CONF_CONNECTED,
CONF_GAS,
CONF_HOT,
CONF_LIGHT,
CONF_LOCKED,
CONF_MOIST,
CONF_MOTION,
CONF_MOVING,
CONF_OCCUPIED,
CONF_OPEN,
CONF_PLUGGED_IN,
CONF_POWERED,
CONF_PRESENT,
CONF_PROBLEM,
CONF_SMOKE,
CONF_SOUND,
CONF_UNSAFE,
CONF_VIBRATION,
CONF_TURNED_ON,
]
TURNED_OFF = [
CONF_NOT_BAT_LOW,
CONF_NOT_COLD,
CONF_NOT_CONNECTED,
CONF_NOT_HOT,
CONF_NOT_LOCKED,
CONF_NOT_MOIST,
CONF_NOT_MOVING,
CONF_NOT_OCCUPIED,
CONF_NOT_OPEN,
CONF_NOT_PLUGGED_IN,
CONF_NOT_POWERED,
CONF_NOT_PRESENT,
CONF_NOT_UNSAFE,
CONF_NO_GAS,
CONF_NO_LIGHT,
CONF_NO_MOTION,
CONF_NO_PROBLEM,
CONF_NO_SMOKE,
CONF_NO_SOUND,
CONF_NO_VIBRATION,
CONF_TURNED_OFF,
]
ENTITY_TRIGGERS = {
DEVICE_CLASS_BATTERY: [{CONF_TYPE: CONF_BAT_LOW}, {CONF_TYPE: CONF_NOT_BAT_LOW}],
DEVICE_CLASS_COLD: [{CONF_TYPE: CONF_COLD}, {CONF_TYPE: CONF_NOT_COLD}],
DEVICE_CLASS_CONNECTIVITY: [
{CONF_TYPE: CONF_CONNECTED},
{CONF_TYPE: CONF_NOT_CONNECTED},
],
DEVICE_CLASS_DOOR: [{CONF_TYPE: CONF_OPEN}, {CONF_TYPE: CONF_NOT_OPEN}],
DEVICE_CLASS_GARAGE_DOOR: [{CONF_TYPE: CONF_OPEN}, {CONF_TYPE: CONF_NOT_OPEN}],
DEVICE_CLASS_GAS: [{CONF_TYPE: CONF_GAS}, {CONF_TYPE: CONF_NO_GAS}],
DEVICE_CLASS_HEAT: [{CONF_TYPE: CONF_HOT}, {CONF_TYPE: CONF_NOT_HOT}],
DEVICE_CLASS_LIGHT: [{CONF_TYPE: CONF_LIGHT}, {CONF_TYPE: CONF_NO_LIGHT}],
DEVICE_CLASS_LOCK: [{CONF_TYPE: CONF_LOCKED}, {CONF_TYPE: CONF_NOT_LOCKED}],
DEVICE_CLASS_MOISTURE: [{CONF_TYPE: CONF_MOIST}, {CONF_TYPE: CONF_NOT_MOIST}],
DEVICE_CLASS_MOTION: [{CONF_TYPE: CONF_MOTION}, {CONF_TYPE: CONF_NO_MOTION}],
DEVICE_CLASS_MOVING: [{CONF_TYPE: CONF_MOVING}, {CONF_TYPE: CONF_NOT_MOVING}],
DEVICE_CLASS_OCCUPANCY: [
{CONF_TYPE: CONF_OCCUPIED},
{CONF_TYPE: CONF_NOT_OCCUPIED},
],
DEVICE_CLASS_OPENING: [{CONF_TYPE: CONF_OPEN}, {CONF_TYPE: CONF_NOT_OPEN}],
DEVICE_CLASS_PLUG: [{CONF_TYPE: CONF_PLUGGED_IN}, {CONF_TYPE: CONF_NOT_PLUGGED_IN}],
DEVICE_CLASS_POWER: [{CONF_TYPE: CONF_POWERED}, {CONF_TYPE: CONF_NOT_POWERED}],
DEVICE_CLASS_PRESENCE: [{CONF_TYPE: CONF_PRESENT}, {CONF_TYPE: CONF_NOT_PRESENT}],
DEVICE_CLASS_PROBLEM: [{CONF_TYPE: CONF_PROBLEM}, {CONF_TYPE: CONF_NO_PROBLEM}],
DEVICE_CLASS_SAFETY: [{CONF_TYPE: CONF_UNSAFE}, {CONF_TYPE: CONF_NOT_UNSAFE}],
DEVICE_CLASS_SMOKE: [{CONF_TYPE: CONF_SMOKE}, {CONF_TYPE: CONF_NO_SMOKE}],
DEVICE_CLASS_SOUND: [{CONF_TYPE: CONF_SOUND}, {CONF_TYPE: CONF_NO_SOUND}],
DEVICE_CLASS_VIBRATION: [
{CONF_TYPE: CONF_VIBRATION},
{CONF_TYPE: CONF_NO_VIBRATION},
],
DEVICE_CLASS_WINDOW: [{CONF_TYPE: CONF_OPEN}, {CONF_TYPE: CONF_NOT_OPEN}],
DEVICE_CLASS_NONE: [{CONF_TYPE: CONF_TURNED_ON}, {CONF_TYPE: CONF_TURNED_OFF}],
}
TRIGGER_SCHEMA = TRIGGER_BASE_SCHEMA.extend(
{
vol.Required(CONF_ENTITY_ID): cv.entity_id,
vol.Required(CONF_TYPE): vol.In(TURNED_OFF + TURNED_ON),
}
)
async def async_attach_trigger(hass, config, action, automation_info):
"""Listen for state changes based on configuration."""
config = TRIGGER_SCHEMA(config)
trigger_type = config[CONF_TYPE]
if trigger_type in TURNED_ON:
from_state = "off"
to_state = "on"
else:
from_state = "on"
to_state = "off"
state_config = {
state_automation.CONF_ENTITY_ID: config[CONF_ENTITY_ID],
state_automation.CONF_FROM: from_state,
state_automation.CONF_TO: to_state,
}
return await state_automation.async_attach_trigger(
hass, state_config, action, automation_info, platform_type="device"
)
async def async_get_triggers(hass, device_id):
"""List device triggers."""
triggers = []
entity_registry = await hass.helpers.entity_registry.async_get_registry()
entries = [
entry
for entry in async_entries_for_device(entity_registry, device_id)
if entry.domain == DOMAIN
]
for entry in entries:
device_class = None
state = hass.states.get(entry.entity_id)
if state:
device_class = state.attributes.get(ATTR_DEVICE_CLASS)
templates = ENTITY_TRIGGERS.get(
device_class, ENTITY_TRIGGERS[DEVICE_CLASS_NONE]
)
triggers.extend(
(
{
**automation,
"platform": "device",
"device_id": device_id,
"entity_id": entry.entity_id,
"domain": DOMAIN,
}
for automation in templates
)
)
return triggers

View file

@ -3,6 +3,7 @@ import voluptuous as vol
import homeassistant.components.automation.event as event import homeassistant.components.automation.event as event
from homeassistant.components.device_automation import TRIGGER_BASE_SCHEMA
from homeassistant.components.device_automation.exceptions import ( from homeassistant.components.device_automation.exceptions import (
InvalidDeviceAutomationConfig, InvalidDeviceAutomationConfig,
) )
@ -171,16 +172,8 @@ REMOTES = {
AQARA_SQUARE_SWITCH_MODEL: AQARA_SQUARE_SWITCH, AQARA_SQUARE_SWITCH_MODEL: AQARA_SQUARE_SWITCH,
} }
TRIGGER_SCHEMA = vol.All( TRIGGER_SCHEMA = TRIGGER_BASE_SCHEMA.extend(
vol.Schema( {vol.Required(CONF_TYPE): str, vol.Required(CONF_SUBTYPE): str}
{
vol.Required(CONF_DEVICE_ID): str,
vol.Required(CONF_DOMAIN): DOMAIN,
vol.Required(CONF_PLATFORM): "device",
vol.Required(CONF_TYPE): str,
vol.Required(CONF_SUBTYPE): str,
}
)
) )
@ -198,7 +191,7 @@ def _get_deconz_event_from_device_id(hass, device_id):
return None return None
async def async_trigger(hass, config, action, automation_info): async def async_attach_trigger(hass, config, action, automation_info):
"""Listen for state changes based on configuration.""" """Listen for state changes based on configuration."""
config = TRIGGER_SCHEMA(config) config = TRIGGER_SCHEMA(config)
@ -223,7 +216,9 @@ async def async_trigger(hass, config, action, automation_info):
event.CONF_EVENT_DATA: {CONF_UNIQUE_ID: event_id, CONF_EVENT: trigger}, event.CONF_EVENT_DATA: {CONF_UNIQUE_ID: event_id, CONF_EVENT: trigger},
} }
return await event.async_trigger(hass, state_config, action, automation_info) return await event.async_attach_trigger(
hass, state_config, action, automation_info, platform_type="device"
)
async def async_get_triggers(hass, device_id): async def async_get_triggers(hass, device_id):

View file

@ -1,16 +1,12 @@
"""Helpers for device automations.""" """Helpers for device automations."""
import asyncio import asyncio
import logging import logging
from typing import Callable, cast
import voluptuous as vol import voluptuous as vol
from homeassistant.const import CONF_PLATFORM, CONF_DOMAIN, CONF_DEVICE_ID
from homeassistant.components import websocket_api from homeassistant.components import websocket_api
from homeassistant.const import CONF_DOMAIN
from homeassistant.core import split_entity_id, HomeAssistant
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity_registry import async_entries_for_device from homeassistant.helpers.entity_registry import async_entries_for_device
from homeassistant.helpers.typing import ConfigType
from homeassistant.loader import async_get_integration, IntegrationNotFound from homeassistant.loader import async_get_integration, IntegrationNotFound
DOMAIN = "device_automation" DOMAIN = "device_automation"
@ -18,6 +14,21 @@ DOMAIN = "device_automation"
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
TRIGGER_BASE_SCHEMA = vol.Schema(
{
vol.Required(CONF_PLATFORM): "device",
vol.Required(CONF_DOMAIN): str,
vol.Required(CONF_DEVICE_ID): str,
}
)
TYPES = {
"trigger": ("device_trigger", "async_get_triggers"),
"condition": ("device_condition", "async_get_conditions"),
"action": ("device_action", "async_get_actions"),
}
async def async_setup(hass, config): async def async_setup(hass, config):
"""Set up device automation.""" """Set up device automation."""
hass.components.websocket_api.async_register_command( hass.components.websocket_api.async_register_command(
@ -32,21 +43,9 @@ async def async_setup(hass, config):
return True return True
async def async_device_condition_from_config( async def _async_get_device_automations_from_domain(
hass: HomeAssistant, config: ConfigType, config_validation: bool = True hass, domain, automation_type, device_id
) -> Callable[..., bool]: ):
"""Wrap action method with state based condition."""
if config_validation:
config = cv.DEVICE_CONDITION_SCHEMA(config)
integration = await async_get_integration(hass, config[CONF_DOMAIN])
platform = integration.get_platform("device_automation")
return cast(
Callable[..., bool],
platform.async_condition_from_config(config, config_validation), # type: ignore
)
async def _async_get_device_automations_from_domain(hass, domain, fname, device_id):
"""List device automations.""" """List device automations."""
integration = None integration = None
try: try:
@ -55,17 +54,18 @@ async def _async_get_device_automations_from_domain(hass, domain, fname, device_
_LOGGER.warning("Integration %s not found", domain) _LOGGER.warning("Integration %s not found", domain)
return None return None
platform_name, function_name = TYPES[automation_type]
try: try:
platform = integration.get_platform("device_automation") platform = integration.get_platform(platform_name)
except ImportError: except ImportError:
# The domain does not have device automations # The domain does not have device automations
return None return None
if hasattr(platform, fname): return await getattr(platform, function_name)(hass, device_id)
return await getattr(platform, fname)(hass, device_id)
async def _async_get_device_automations(hass, fname, device_id): async def _async_get_device_automations(hass, automation_type, device_id):
"""List device automations.""" """List device automations."""
device_registry, entity_registry = await asyncio.gather( device_registry, entity_registry = await asyncio.gather(
hass.helpers.device_registry.async_get_registry(), hass.helpers.device_registry.async_get_registry(),
@ -79,13 +79,15 @@ async def _async_get_device_automations(hass, fname, device_id):
config_entry = hass.config_entries.async_get_entry(entry_id) config_entry = hass.config_entries.async_get_entry(entry_id)
domains.add(config_entry.domain) domains.add(config_entry.domain)
entities = async_entries_for_device(entity_registry, device_id) entity_entries = async_entries_for_device(entity_registry, device_id)
for entity in entities: for entity_entry in entity_entries:
domains.add(split_entity_id(entity.entity_id)[0]) domains.add(entity_entry.domain)
device_automations = await asyncio.gather( device_automations = await asyncio.gather(
*( *(
_async_get_device_automations_from_domain(hass, domain, fname, device_id) _async_get_device_automations_from_domain(
hass, domain, automation_type, device_id
)
for domain in domains for domain in domains
) )
) )
@ -106,7 +108,7 @@ async def _async_get_device_automations(hass, fname, device_id):
async def websocket_device_automation_list_actions(hass, connection, msg): async def websocket_device_automation_list_actions(hass, connection, msg):
"""Handle request for device actions.""" """Handle request for device actions."""
device_id = msg["device_id"] device_id = msg["device_id"]
actions = await _async_get_device_automations(hass, "async_get_actions", device_id) actions = await _async_get_device_automations(hass, "action", device_id)
connection.send_result(msg["id"], actions) connection.send_result(msg["id"], actions)
@ -120,9 +122,7 @@ async def websocket_device_automation_list_actions(hass, connection, msg):
async def websocket_device_automation_list_conditions(hass, connection, msg): async def websocket_device_automation_list_conditions(hass, connection, msg):
"""Handle request for device conditions.""" """Handle request for device conditions."""
device_id = msg["device_id"] device_id = msg["device_id"]
conditions = await _async_get_device_automations( conditions = await _async_get_device_automations(hass, "condition", device_id)
hass, "async_get_conditions", device_id
)
connection.send_result(msg["id"], conditions) connection.send_result(msg["id"], conditions)
@ -136,7 +136,5 @@ async def websocket_device_automation_list_conditions(hass, connection, msg):
async def websocket_device_automation_list_triggers(hass, connection, msg): async def websocket_device_automation_list_triggers(hass, connection, msg):
"""Handle request for device triggers.""" """Handle request for device triggers."""
device_id = msg["device_id"] device_id = msg["device_id"]
triggers = await _async_get_device_automations( triggers = await _async_get_device_automations(hass, "trigger", device_id)
hass, "async_get_triggers", device_id
)
connection.send_result(msg["id"], triggers) connection.send_result(msg["id"], triggers)

View file

@ -1,7 +1,9 @@
"""Device automation helpers for toggle entity.""" """Device automation helpers for toggle entity."""
from typing import List
import voluptuous as vol import voluptuous as vol
import homeassistant.components.automation.state as state from homeassistant.core import Context, HomeAssistant, CALLBACK_TYPE
from homeassistant.components.automation import state, AutomationActionType
from homeassistant.components.device_automation.const import ( from homeassistant.components.device_automation.const import (
CONF_IS_OFF, CONF_IS_OFF,
CONF_IS_ON, CONF_IS_ON,
@ -11,17 +13,11 @@ from homeassistant.components.device_automation.const import (
CONF_TURNED_OFF, CONF_TURNED_OFF,
CONF_TURNED_ON, CONF_TURNED_ON,
) )
from homeassistant.core import split_entity_id from homeassistant.const import CONF_CONDITION, CONF_ENTITY_ID, CONF_PLATFORM, CONF_TYPE
from homeassistant.const import (
CONF_CONDITION,
CONF_DEVICE_ID,
CONF_DOMAIN,
CONF_ENTITY_ID,
CONF_PLATFORM,
CONF_TYPE,
)
from homeassistant.helpers.entity_registry import async_entries_for_device from homeassistant.helpers.entity_registry import async_entries_for_device
from homeassistant.helpers import condition, config_validation as cv, service from homeassistant.helpers import condition, config_validation as cv, service
from homeassistant.helpers.typing import ConfigType, TemplateVarsType
from . import TRIGGER_BASE_SCHEMA
ENTITY_ACTIONS = [ ENTITY_ACTIONS = [
{ {
@ -64,41 +60,35 @@ ENTITY_TRIGGERS = [
}, },
] ]
ACTION_SCHEMA = vol.Schema( ACTION_SCHEMA = cv.DEVICE_ACTION_BASE_SCHEMA.extend(
{ {
vol.Required(CONF_DEVICE_ID): str,
vol.Required(CONF_DOMAIN): str,
vol.Required(CONF_ENTITY_ID): cv.entity_id, vol.Required(CONF_ENTITY_ID): cv.entity_id,
vol.Required(CONF_TYPE): vol.In([CONF_TOGGLE, CONF_TURN_OFF, CONF_TURN_ON]), vol.Required(CONF_TYPE): vol.In([CONF_TOGGLE, CONF_TURN_OFF, CONF_TURN_ON]),
} }
) )
CONDITION_SCHEMA = vol.Schema( CONDITION_SCHEMA = cv.DEVICE_CONDITION_BASE_SCHEMA.extend(
{ {
vol.Required(CONF_CONDITION): "device",
vol.Required(CONF_DEVICE_ID): str,
vol.Required(CONF_DOMAIN): str,
vol.Required(CONF_ENTITY_ID): cv.entity_id, vol.Required(CONF_ENTITY_ID): cv.entity_id,
vol.Required(CONF_TYPE): vol.In([CONF_IS_OFF, CONF_IS_ON]), vol.Required(CONF_TYPE): vol.In([CONF_IS_OFF, CONF_IS_ON]),
} }
) )
TRIGGER_SCHEMA = vol.Schema( TRIGGER_SCHEMA = TRIGGER_BASE_SCHEMA.extend(
{ {
vol.Required(CONF_PLATFORM): "device",
vol.Required(CONF_DEVICE_ID): str,
vol.Required(CONF_DOMAIN): str,
vol.Required(CONF_ENTITY_ID): cv.entity_id, vol.Required(CONF_ENTITY_ID): cv.entity_id,
vol.Required(CONF_TYPE): vol.In([CONF_TURNED_OFF, CONF_TURNED_ON]), vol.Required(CONF_TYPE): vol.In([CONF_TURNED_OFF, CONF_TURNED_ON]),
} }
) )
def _is_domain(entity, domain): async def async_call_action_from_config(
return split_entity_id(entity.entity_id)[0] == domain hass: HomeAssistant,
config: ConfigType,
variables: TemplateVarsType,
async def async_call_action_from_config(hass, config, variables, context, domain): context: Context,
domain: str,
):
"""Change state based on configuration.""" """Change state based on configuration."""
config = ACTION_SCHEMA(config) config = ACTION_SCHEMA(config)
action_type = config[CONF_TYPE] action_type = config[CONF_TYPE]
@ -119,7 +109,9 @@ async def async_call_action_from_config(hass, config, variables, context, domain
) )
def async_condition_from_config(config, config_validation): def async_condition_from_config(
config: ConfigType, config_validation: bool
) -> condition.ConditionCheckerType:
"""Evaluate state based on configuration.""" """Evaluate state based on configuration."""
condition_type = config[CONF_TYPE] condition_type = config[CONF_TYPE]
if condition_type == CONF_IS_ON: if condition_type == CONF_IS_ON:
@ -135,7 +127,12 @@ def async_condition_from_config(config, config_validation):
return condition.state_from_config(state_config, config_validation) return condition.state_from_config(state_config, config_validation)
async def async_attach_trigger(hass, config, action, automation_info): async def async_attach_trigger(
hass: HomeAssistant,
config: ConfigType,
action: AutomationActionType,
automation_info: dict,
) -> CALLBACK_TYPE:
"""Listen for state changes based on configuration.""" """Listen for state changes based on configuration."""
trigger_type = config[CONF_TYPE] trigger_type = config[CONF_TYPE]
if trigger_type == CONF_TURNED_ON: if trigger_type == CONF_TURNED_ON:
@ -150,37 +147,56 @@ async def async_attach_trigger(hass, config, action, automation_info):
state.CONF_TO: to_state, state.CONF_TO: to_state,
} }
return await state.async_trigger(hass, state_config, action, automation_info) return await state.async_attach_trigger(
hass, state_config, action, automation_info, platform_type="device"
)
async def _async_get_automations(hass, device_id, automation_templates, domain): async def _async_get_automations(
hass: HomeAssistant, device_id: str, automation_templates: List[dict], domain: str
) -> List[dict]:
"""List device automations.""" """List device automations."""
automations = [] automations = []
entity_registry = await hass.helpers.entity_registry.async_get_registry() entity_registry = await hass.helpers.entity_registry.async_get_registry()
entities = async_entries_for_device(entity_registry, device_id) entries = [
domain_entities = [x for x in entities if _is_domain(x, domain)] entry
for entity in domain_entities: for entry in async_entries_for_device(entity_registry, device_id)
for automation in automation_templates: if entry.domain == domain
automation = dict(automation) ]
automation.update(
device_id=device_id, entity_id=entity.entity_id, domain=domain for entry in entries:
automations.extend(
(
{
**template,
"device_id": device_id,
"entity_id": entry.entity_id,
"domain": domain,
}
for template in automation_templates
) )
automations.append(automation) )
return automations return automations
async def async_get_actions(hass, device_id, domain): async def async_get_actions(
hass: HomeAssistant, device_id: str, domain: str
) -> List[dict]:
"""List device actions.""" """List device actions."""
return await _async_get_automations(hass, device_id, ENTITY_ACTIONS, domain) return await _async_get_automations(hass, device_id, ENTITY_ACTIONS, domain)
async def async_get_conditions(hass, device_id, domain): async def async_get_conditions(
hass: HomeAssistant, device_id: str, domain: str
) -> List[dict]:
"""List device conditions.""" """List device conditions."""
return await _async_get_automations(hass, device_id, ENTITY_CONDITIONS, domain) return await _async_get_automations(hass, device_id, ENTITY_CONDITIONS, domain)
async def async_get_triggers(hass, device_id, domain): async def async_get_triggers(
hass: HomeAssistant, device_id: str, domain: str
) -> List[dict]:
"""List device triggers.""" """List device triggers."""
return await _async_get_automations(hass, device_id, ENTITY_TRIGGERS, domain) return await _async_get_automations(hass, device_id, ENTITY_TRIGGERS, domain)

View file

@ -0,0 +1,30 @@
"""Provides device actions for lights."""
from typing import List
import voluptuous as vol
from homeassistant.core import HomeAssistant, Context
from homeassistant.components.device_automation import toggle_entity
from homeassistant.const import CONF_DOMAIN
from homeassistant.helpers.typing import TemplateVarsType, ConfigType
from . import DOMAIN
ACTION_SCHEMA = toggle_entity.ACTION_SCHEMA.extend({vol.Required(CONF_DOMAIN): DOMAIN})
async def async_call_action_from_config(
hass: HomeAssistant,
config: ConfigType,
variables: TemplateVarsType,
context: Context,
) -> None:
"""Change state based on configuration."""
config = ACTION_SCHEMA(config)
await toggle_entity.async_call_action_from_config(
hass, config, variables, context, DOMAIN
)
async def async_get_actions(hass: HomeAssistant, device_id: str) -> List[dict]:
"""List device actions."""
return await toggle_entity.async_get_actions(hass, device_id, DOMAIN)

View file

@ -1,56 +0,0 @@
"""Provides device automations for lights."""
import voluptuous as vol
from homeassistant.components.device_automation import toggle_entity
from homeassistant.const import CONF_DOMAIN
from . import DOMAIN
# mypy: allow-untyped-defs, no-check-untyped-defs
ACTION_SCHEMA = toggle_entity.ACTION_SCHEMA.extend({vol.Required(CONF_DOMAIN): DOMAIN})
CONDITION_SCHEMA = toggle_entity.CONDITION_SCHEMA.extend(
{vol.Required(CONF_DOMAIN): DOMAIN}
)
TRIGGER_SCHEMA = toggle_entity.TRIGGER_SCHEMA.extend(
{vol.Required(CONF_DOMAIN): DOMAIN}
)
async def async_call_action_from_config(hass, config, variables, context):
"""Change state based on configuration."""
config = ACTION_SCHEMA(config)
await toggle_entity.async_call_action_from_config(
hass, config, variables, context, DOMAIN
)
def async_condition_from_config(config, config_validation):
"""Evaluate state based on configuration."""
config = CONDITION_SCHEMA(config)
return toggle_entity.async_condition_from_config(config, config_validation)
async def async_trigger(hass, config, action, automation_info):
"""Listen for state changes based on configuration."""
config = TRIGGER_SCHEMA(config)
return await toggle_entity.async_attach_trigger(
hass, config, action, automation_info
)
async def async_get_actions(hass, device_id):
"""List device actions."""
return await toggle_entity.async_get_actions(hass, device_id, DOMAIN)
async def async_get_conditions(hass, device_id):
"""List device conditions."""
return await toggle_entity.async_get_conditions(hass, device_id, DOMAIN)
async def async_get_triggers(hass, device_id):
"""List device triggers."""
return await toggle_entity.async_get_triggers(hass, device_id, DOMAIN)

View file

@ -0,0 +1,28 @@
"""Provides device conditions for lights."""
from typing import List
import voluptuous as vol
from homeassistant.core import HomeAssistant
from homeassistant.components.device_automation import toggle_entity
from homeassistant.const import CONF_DOMAIN
from homeassistant.helpers.typing import ConfigType
from homeassistant.helpers.condition import ConditionCheckerType
from . import DOMAIN
CONDITION_SCHEMA = toggle_entity.CONDITION_SCHEMA.extend(
{vol.Required(CONF_DOMAIN): DOMAIN}
)
def async_condition_from_config(
config: ConfigType, config_validation: bool
) -> ConditionCheckerType:
"""Evaluate state based on configuration."""
config = CONDITION_SCHEMA(config)
return toggle_entity.async_condition_from_config(config, config_validation)
async def async_get_conditions(hass: HomeAssistant, device_id: str) -> List[dict]:
"""List device conditions."""
return await toggle_entity.async_get_conditions(hass, device_id, DOMAIN)

View file

@ -0,0 +1,33 @@
"""Provides device trigger for lights."""
from typing import List
import voluptuous as vol
from homeassistant.core import HomeAssistant, CALLBACK_TYPE
from homeassistant.components.automation import AutomationActionType
from homeassistant.components.device_automation import toggle_entity
from homeassistant.const import CONF_DOMAIN
from homeassistant.helpers.typing import ConfigType
from . import DOMAIN
TRIGGER_SCHEMA = toggle_entity.TRIGGER_SCHEMA.extend(
{vol.Required(CONF_DOMAIN): DOMAIN}
)
async def async_attach_trigger(
hass: HomeAssistant,
config: ConfigType,
action: AutomationActionType,
automation_info: dict,
) -> CALLBACK_TYPE:
"""Listen for state changes based on configuration."""
config = TRIGGER_SCHEMA(config)
return await toggle_entity.async_attach_trigger(
hass, config, action, automation_info
)
async def async_get_triggers(hass: HomeAssistant, device_id: str) -> List[dict]:
"""List device triggers."""
return await toggle_entity.async_get_triggers(hass, device_id, DOMAIN)

View file

@ -0,0 +1,30 @@
"""Provides device actions for switches."""
from typing import List
import voluptuous as vol
from homeassistant.core import HomeAssistant, Context
from homeassistant.components.device_automation import toggle_entity
from homeassistant.const import CONF_DOMAIN
from homeassistant.helpers.typing import TemplateVarsType, ConfigType
from . import DOMAIN
ACTION_SCHEMA = toggle_entity.ACTION_SCHEMA.extend({vol.Required(CONF_DOMAIN): DOMAIN})
async def async_call_action_from_config(
hass: HomeAssistant,
config: ConfigType,
variables: TemplateVarsType,
context: Context,
) -> None:
"""Change state based on configuration."""
config = ACTION_SCHEMA(config)
await toggle_entity.async_call_action_from_config(
hass, config, variables, context, DOMAIN
)
async def async_get_actions(hass: HomeAssistant, device_id: str) -> List[dict]:
"""List device actions."""
return await toggle_entity.async_get_actions(hass, device_id, DOMAIN)

View file

@ -1,56 +0,0 @@
"""Provides device automations for lights."""
import voluptuous as vol
from homeassistant.components.device_automation import toggle_entity
from homeassistant.const import CONF_DOMAIN
from . import DOMAIN
# mypy: allow-untyped-defs, no-check-untyped-defs
ACTION_SCHEMA = toggle_entity.ACTION_SCHEMA.extend({vol.Required(CONF_DOMAIN): DOMAIN})
CONDITION_SCHEMA = toggle_entity.CONDITION_SCHEMA.extend(
{vol.Required(CONF_DOMAIN): DOMAIN}
)
TRIGGER_SCHEMA = toggle_entity.TRIGGER_SCHEMA.extend(
{vol.Required(CONF_DOMAIN): DOMAIN}
)
async def async_call_action_from_config(hass, config, variables, context):
"""Change state based on configuration."""
config = ACTION_SCHEMA(config)
await toggle_entity.async_call_action_from_config(
hass, config, variables, context, DOMAIN
)
def async_condition_from_config(config, config_validation):
"""Evaluate state based on configuration."""
config = CONDITION_SCHEMA(config)
return toggle_entity.async_condition_from_config(config, config_validation)
async def async_trigger(hass, config, action, automation_info):
"""Listen for state changes based on configuration."""
config = TRIGGER_SCHEMA(config)
return await toggle_entity.async_attach_trigger(
hass, config, action, automation_info
)
async def async_get_actions(hass, device_id):
"""List device actions."""
return await toggle_entity.async_get_actions(hass, device_id, DOMAIN)
async def async_get_conditions(hass, device_id):
"""List device conditions."""
return await toggle_entity.async_get_conditions(hass, device_id, DOMAIN)
async def async_get_triggers(hass, device_id):
"""List device triggers."""
return await toggle_entity.async_get_triggers(hass, device_id, DOMAIN)

View file

@ -0,0 +1,28 @@
"""Provides device conditions for switches."""
from typing import List
import voluptuous as vol
from homeassistant.core import HomeAssistant
from homeassistant.components.device_automation import toggle_entity
from homeassistant.const import CONF_DOMAIN
from homeassistant.helpers.typing import ConfigType
from homeassistant.helpers.condition import ConditionCheckerType
from . import DOMAIN
CONDITION_SCHEMA = toggle_entity.CONDITION_SCHEMA.extend(
{vol.Required(CONF_DOMAIN): DOMAIN}
)
def async_condition_from_config(
config: ConfigType, config_validation: bool
) -> ConditionCheckerType:
"""Evaluate state based on configuration."""
config = CONDITION_SCHEMA(config)
return toggle_entity.async_condition_from_config(config, config_validation)
async def async_get_conditions(hass: HomeAssistant, device_id: str) -> List[dict]:
"""List device conditions."""
return await toggle_entity.async_get_conditions(hass, device_id, DOMAIN)

View file

@ -0,0 +1,33 @@
"""Provides device triggers for switches."""
from typing import List
import voluptuous as vol
from homeassistant.core import HomeAssistant, CALLBACK_TYPE
from homeassistant.components.automation import AutomationActionType
from homeassistant.components.device_automation import toggle_entity
from homeassistant.const import CONF_DOMAIN
from homeassistant.helpers.typing import ConfigType
from . import DOMAIN
TRIGGER_SCHEMA = toggle_entity.TRIGGER_SCHEMA.extend(
{vol.Required(CONF_DOMAIN): DOMAIN}
)
async def async_attach_trigger(
hass: HomeAssistant,
config: ConfigType,
action: AutomationActionType,
automation_info: dict,
) -> CALLBACK_TYPE:
"""Listen for state changes based on configuration."""
config = TRIGGER_SCHEMA(config)
return await toggle_entity.async_attach_trigger(
hass, config, action, automation_info
)
async def async_get_triggers(hass: HomeAssistant, device_id: str) -> List[dict]:
"""List device triggers."""
return await toggle_entity.async_get_triggers(hass, device_id, DOMAIN)

View file

@ -6,6 +6,7 @@ from homeassistant.components.device_automation.exceptions import (
InvalidDeviceAutomationConfig, InvalidDeviceAutomationConfig,
) )
from homeassistant.const import CONF_DEVICE_ID, CONF_DOMAIN, CONF_PLATFORM, CONF_TYPE from homeassistant.const import CONF_DEVICE_ID, CONF_DOMAIN, CONF_PLATFORM, CONF_TYPE
from homeassistant.components.device_automation import TRIGGER_BASE_SCHEMA
from . import DOMAIN from . import DOMAIN
from .core.const import DATA_ZHA, DATA_ZHA_GATEWAY from .core.const import DATA_ZHA, DATA_ZHA_GATEWAY
@ -16,20 +17,12 @@ DEVICE = "device"
DEVICE_IEEE = "device_ieee" DEVICE_IEEE = "device_ieee"
ZHA_EVENT = "zha_event" ZHA_EVENT = "zha_event"
TRIGGER_SCHEMA = vol.All( TRIGGER_SCHEMA = TRIGGER_BASE_SCHEMA.extend(
vol.Schema( {vol.Required(CONF_TYPE): str, vol.Required(CONF_SUBTYPE): str}
{
vol.Required(CONF_DEVICE_ID): str,
vol.Required(CONF_DOMAIN): DOMAIN,
vol.Required(CONF_PLATFORM): DEVICE,
vol.Required(CONF_TYPE): str,
vol.Required(CONF_SUBTYPE): str,
}
)
) )
async def async_trigger(hass, config, action, automation_info): async def async_attach_trigger(hass, config, action, automation_info):
"""Listen for state changes based on configuration.""" """Listen for state changes based on configuration."""
config = TRIGGER_SCHEMA(config) config = TRIGGER_SCHEMA(config)
trigger = (config[CONF_TYPE], config[CONF_SUBTYPE]) trigger = (config[CONF_TYPE], config[CONF_SUBTYPE])
@ -48,7 +41,9 @@ async def async_trigger(hass, config, action, automation_info):
event.CONF_EVENT_DATA: {DEVICE_IEEE: str(zha_device.ieee), **trigger}, event.CONF_EVENT_DATA: {DEVICE_IEEE: str(zha_device.ieee), **trigger},
} }
return await event.async_trigger(hass, state_config, action, automation_info) return await event.async_attach_trigger(
hass, state_config, action, automation_info, platform_type="device"
)
async def async_get_triggers(hass, device_id): async def async_get_triggers(hass, device_id):

View file

@ -8,16 +8,14 @@ from typing import Callable, Container, Optional, Union, cast
from homeassistant.helpers.template import Template from homeassistant.helpers.template import Template
from homeassistant.helpers.typing import ConfigType, TemplateVarsType from homeassistant.helpers.typing import ConfigType, TemplateVarsType
from homeassistant.loader import async_get_integration
from homeassistant.core import HomeAssistant, State from homeassistant.core import HomeAssistant, State
from homeassistant.components import zone as zone_cmp from homeassistant.components import zone as zone_cmp
from homeassistant.components.device_automation import ( # noqa: F401 pylint: disable=unused-import
async_device_condition_from_config as async_device_from_config,
)
from homeassistant.const import ( from homeassistant.const import (
ATTR_GPS_ACCURACY, ATTR_GPS_ACCURACY,
ATTR_LATITUDE, ATTR_LATITUDE,
ATTR_LONGITUDE, ATTR_LONGITUDE,
CONF_DOMAIN,
CONF_ENTITY_ID, CONF_ENTITY_ID,
CONF_VALUE_TEMPLATE, CONF_VALUE_TEMPLATE,
CONF_CONDITION, CONF_CONDITION,
@ -45,10 +43,12 @@ ASYNC_FROM_CONFIG_FORMAT = "async_{}_from_config"
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
ConditionCheckerType = Callable[[HomeAssistant, TemplateVarsType], bool]
async def async_from_config( async def async_from_config(
hass: HomeAssistant, config: ConfigType, config_validation: bool = True hass: HomeAssistant, config: ConfigType, config_validation: bool = True
) -> Callable[..., bool]: ) -> ConditionCheckerType:
"""Turn a condition configuration into a method. """Turn a condition configuration into a method.
Should be run on the event loop. Should be run on the event loop.
@ -74,13 +74,15 @@ async def async_from_config(
check_factory = check_factory.func check_factory = check_factory.func
if asyncio.iscoroutinefunction(check_factory): if asyncio.iscoroutinefunction(check_factory):
return cast(Callable[..., bool], await factory(hass, config, config_validation)) return cast(
return cast(Callable[..., bool], factory(config, config_validation)) ConditionCheckerType, await factory(hass, config, config_validation)
)
return cast(ConditionCheckerType, factory(config, config_validation))
async def async_and_from_config( async def async_and_from_config(
hass: HomeAssistant, config: ConfigType, config_validation: bool = True hass: HomeAssistant, config: ConfigType, config_validation: bool = True
) -> Callable[..., bool]: ) -> ConditionCheckerType:
"""Create multi condition matcher using 'AND'.""" """Create multi condition matcher using 'AND'."""
if config_validation: if config_validation:
config = cv.AND_CONDITION_SCHEMA(config) config = cv.AND_CONDITION_SCHEMA(config)
@ -107,7 +109,7 @@ async def async_and_from_config(
async def async_or_from_config( async def async_or_from_config(
hass: HomeAssistant, config: ConfigType, config_validation: bool = True hass: HomeAssistant, config: ConfigType, config_validation: bool = True
) -> Callable[..., bool]: ) -> ConditionCheckerType:
"""Create multi condition matcher using 'OR'.""" """Create multi condition matcher using 'OR'."""
if config_validation: if config_validation:
config = cv.OR_CONDITION_SCHEMA(config) config = cv.OR_CONDITION_SCHEMA(config)
@ -205,7 +207,7 @@ def async_numeric_state(
def async_numeric_state_from_config( def async_numeric_state_from_config(
config: ConfigType, config_validation: bool = True config: ConfigType, config_validation: bool = True
) -> Callable[..., bool]: ) -> ConditionCheckerType:
"""Wrap action method with state based condition.""" """Wrap action method with state based condition."""
if config_validation: if config_validation:
config = cv.NUMERIC_STATE_CONDITION_SCHEMA(config) config = cv.NUMERIC_STATE_CONDITION_SCHEMA(config)
@ -255,7 +257,7 @@ def state(
def state_from_config( def state_from_config(
config: ConfigType, config_validation: bool = True config: ConfigType, config_validation: bool = True
) -> Callable[..., bool]: ) -> ConditionCheckerType:
"""Wrap action method with state based condition.""" """Wrap action method with state based condition."""
if config_validation: if config_validation:
config = cv.STATE_CONDITION_SCHEMA(config) config = cv.STATE_CONDITION_SCHEMA(config)
@ -327,7 +329,7 @@ def sun(
def sun_from_config( def sun_from_config(
config: ConfigType, config_validation: bool = True config: ConfigType, config_validation: bool = True
) -> Callable[..., bool]: ) -> ConditionCheckerType:
"""Wrap action method with sun based condition.""" """Wrap action method with sun based condition."""
if config_validation: if config_validation:
config = cv.SUN_CONDITION_SCHEMA(config) config = cv.SUN_CONDITION_SCHEMA(config)
@ -370,7 +372,7 @@ def async_template(
def async_template_from_config( def async_template_from_config(
config: ConfigType, config_validation: bool = True config: ConfigType, config_validation: bool = True
) -> Callable[..., bool]: ) -> ConditionCheckerType:
"""Wrap action method with state based condition.""" """Wrap action method with state based condition."""
if config_validation: if config_validation:
config = cv.TEMPLATE_CONDITION_SCHEMA(config) config = cv.TEMPLATE_CONDITION_SCHEMA(config)
@ -427,7 +429,7 @@ def time(
def time_from_config( def time_from_config(
config: ConfigType, config_validation: bool = True config: ConfigType, config_validation: bool = True
) -> Callable[..., bool]: ) -> ConditionCheckerType:
"""Wrap action method with time based condition.""" """Wrap action method with time based condition."""
if config_validation: if config_validation:
config = cv.TIME_CONDITION_SCHEMA(config) config = cv.TIME_CONDITION_SCHEMA(config)
@ -476,7 +478,7 @@ def zone(
def zone_from_config( def zone_from_config(
config: ConfigType, config_validation: bool = True config: ConfigType, config_validation: bool = True
) -> Callable[..., bool]: ) -> ConditionCheckerType:
"""Wrap action method with zone based condition.""" """Wrap action method with zone based condition."""
if config_validation: if config_validation:
config = cv.ZONE_CONDITION_SCHEMA(config) config = cv.ZONE_CONDITION_SCHEMA(config)
@ -488,3 +490,17 @@ def zone_from_config(
return zone(hass, zone_entity_id, entity_id) return zone(hass, zone_entity_id, entity_id)
return if_in_zone return if_in_zone
async def async_device_from_config(
hass: HomeAssistant, config: ConfigType, config_validation: bool = True
) -> ConditionCheckerType:
"""Test a device condition."""
if config_validation:
config = cv.DEVICE_CONDITION_SCHEMA(config)
integration = await async_get_integration(hass, config[CONF_DOMAIN])
platform = integration.get_platform("device_condition")
return cast(
ConditionCheckerType,
platform.async_condition_from_config(config, config_validation), # type: ignore
)

View file

@ -827,11 +827,16 @@ OR_CONDITION_SCHEMA = vol.Schema(
} }
) )
DEVICE_CONDITION_SCHEMA = vol.Schema( DEVICE_CONDITION_BASE_SCHEMA = vol.Schema(
{vol.Required(CONF_CONDITION): "device", vol.Required(CONF_DOMAIN): str}, {
extra=vol.ALLOW_EXTRA, vol.Required(CONF_CONDITION): "device",
vol.Required(CONF_DEVICE_ID): str,
vol.Required(CONF_DOMAIN): str,
}
) )
DEVICE_CONDITION_SCHEMA = DEVICE_CONDITION_BASE_SCHEMA.extend({}, extra=vol.ALLOW_EXTRA)
CONDITION_SCHEMA: vol.Schema = vol.Any( CONDITION_SCHEMA: vol.Schema = vol.Any(
NUMERIC_STATE_CONDITION_SCHEMA, NUMERIC_STATE_CONDITION_SCHEMA,
STATE_CONDITION_SCHEMA, STATE_CONDITION_SCHEMA,
@ -862,11 +867,12 @@ _SCRIPT_WAIT_TEMPLATE_SCHEMA = vol.Schema(
} }
) )
DEVICE_ACTION_SCHEMA = vol.Schema( DEVICE_ACTION_BASE_SCHEMA = vol.Schema(
{vol.Required(CONF_DEVICE_ID): string, vol.Required(CONF_DOMAIN): str}, {vol.Required(CONF_DEVICE_ID): string, vol.Required(CONF_DOMAIN): str}
extra=vol.ALLOW_EXTRA,
) )
DEVICE_ACTION_SCHEMA = DEVICE_ACTION_BASE_SCHEMA.extend({}, extra=vol.ALLOW_EXTRA)
SCRIPT_SCHEMA = vol.All( SCRIPT_SCHEMA = vol.All(
ensure_list, ensure_list,
[ [

View file

@ -336,7 +336,7 @@ class Script:
self.last_action = action.get(CONF_ALIAS, "device automation") self.last_action = action.get(CONF_ALIAS, "device automation")
self._log("Executing step %s" % self.last_action) self._log("Executing step %s" % self.last_action)
integration = await async_get_integration(self.hass, action[CONF_DOMAIN]) integration = await async_get_integration(self.hass, action[CONF_DOMAIN])
platform = integration.get_platform("device_automation") platform = integration.get_platform("device_action")
await platform.async_call_action_from_config( await platform.async_call_action_from_config(
self.hass, action, variables, context self.hass, action, variables, context
) )

View file

@ -54,7 +54,9 @@ from homeassistant.helpers.json import JSONEncoder
from homeassistant.setup import async_setup_component, setup_component from homeassistant.setup import async_setup_component, setup_component
from homeassistant.util.unit_system import METRIC_SYSTEM from homeassistant.util.unit_system import METRIC_SYSTEM
from homeassistant.util.async_ import run_callback_threadsafe, run_coroutine_threadsafe from homeassistant.util.async_ import run_callback_threadsafe, run_coroutine_threadsafe
from homeassistant.components.device_automation import ( # noqa
_async_get_device_automations as async_get_device_automations,
)
_TEST_INSTANCE_PORT = SERVER_PORT _TEST_INSTANCE_PORT = SERVER_PORT
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)

View file

@ -1,309 +0,0 @@
"""The test for binary_sensor device automation."""
import pytest
from homeassistant.components.binary_sensor import DOMAIN, DEVICE_CLASSES
from homeassistant.components.binary_sensor.device_automation import (
ENTITY_CONDITIONS,
ENTITY_TRIGGERS,
)
from homeassistant.const import STATE_ON, STATE_OFF, CONF_PLATFORM
from homeassistant.setup import async_setup_component
import homeassistant.components.automation as automation
from homeassistant.components.device_automation import (
_async_get_device_automations as async_get_device_automations,
)
from homeassistant.helpers import device_registry
from tests.common import (
MockConfigEntry,
async_mock_service,
mock_device_registry,
mock_registry,
)
@pytest.fixture
def device_reg(hass):
"""Return an empty, loaded, registry."""
return mock_device_registry(hass)
@pytest.fixture
def entity_reg(hass):
"""Return an empty, loaded, registry."""
return mock_registry(hass)
@pytest.fixture
def calls(hass):
"""Track calls to a mock serivce."""
return async_mock_service(hass, "test", "automation")
def _same_lists(a, b):
if len(a) != len(b):
return False
for d in a:
if d not in b:
return False
return True
async def test_get_actions(hass, device_reg, entity_reg):
"""Test we get the expected actions from a binary_sensor."""
platform = getattr(hass.components, f"test.{DOMAIN}")
platform.init()
config_entry = MockConfigEntry(domain="test", data={})
config_entry.add_to_hass(hass)
device_entry = device_reg.async_get_or_create(
config_entry_id=config_entry.entry_id,
connections={(device_registry.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")},
)
entity_reg.async_get_or_create(
DOMAIN,
"test",
platform.ENTITIES["battery"].unique_id,
device_id=device_entry.id,
)
assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}})
expected_actions = []
actions = await async_get_device_automations(
hass, "async_get_actions", device_entry.id
)
assert _same_lists(actions, expected_actions)
async def test_get_conditions(hass, device_reg, entity_reg):
"""Test we get the expected conditions from a binary_sensor."""
platform = getattr(hass.components, f"test.{DOMAIN}")
platform.init()
config_entry = MockConfigEntry(domain="test", data={})
config_entry.add_to_hass(hass)
device_entry = device_reg.async_get_or_create(
config_entry_id=config_entry.entry_id,
connections={(device_registry.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")},
)
for device_class in DEVICE_CLASSES:
entity_reg.async_get_or_create(
DOMAIN,
"test",
platform.ENTITIES[device_class].unique_id,
device_id=device_entry.id,
)
assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}})
expected_conditions = [
{
"condition": "device",
"domain": DOMAIN,
"type": condition["type"],
"device_id": device_entry.id,
"entity_id": platform.ENTITIES[device_class].entity_id,
}
for device_class in DEVICE_CLASSES
for condition in ENTITY_CONDITIONS[device_class]
]
conditions = await async_get_device_automations(
hass, "async_get_conditions", device_entry.id
)
assert _same_lists(conditions, expected_conditions)
async def test_get_triggers(hass, device_reg, entity_reg):
"""Test we get the expected triggers from a binary_sensor."""
platform = getattr(hass.components, f"test.{DOMAIN}")
platform.init()
config_entry = MockConfigEntry(domain="test", data={})
config_entry.add_to_hass(hass)
device_entry = device_reg.async_get_or_create(
config_entry_id=config_entry.entry_id,
connections={(device_registry.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")},
)
for device_class in DEVICE_CLASSES:
entity_reg.async_get_or_create(
DOMAIN,
"test",
platform.ENTITIES[device_class].unique_id,
device_id=device_entry.id,
)
assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}})
expected_triggers = [
{
"platform": "device",
"domain": DOMAIN,
"type": trigger["type"],
"device_id": device_entry.id,
"entity_id": platform.ENTITIES[device_class].entity_id,
}
for device_class in DEVICE_CLASSES
for trigger in ENTITY_TRIGGERS[device_class]
]
triggers = await async_get_device_automations(
hass, "async_get_triggers", device_entry.id
)
assert _same_lists(triggers, expected_triggers)
async def test_if_fires_on_state_change(hass, calls):
"""Test for on and off triggers firing."""
platform = getattr(hass.components, f"test.{DOMAIN}")
platform.init()
assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}})
sensor1 = platform.ENTITIES["battery"]
assert await async_setup_component(
hass,
automation.DOMAIN,
{
automation.DOMAIN: [
{
"trigger": {
"platform": "device",
"domain": DOMAIN,
"device_id": "",
"entity_id": sensor1.entity_id,
"type": "bat_low",
},
"action": {
"service": "test.automation",
"data_template": {
"some": "bat_low {{ trigger.%s }}"
% "}} - {{ trigger.".join(
(
"platform",
"entity_id",
"from_state.state",
"to_state.state",
"for",
)
)
},
},
},
{
"trigger": {
"platform": "device",
"domain": DOMAIN,
"device_id": "",
"entity_id": sensor1.entity_id,
"type": "not_bat_low",
},
"action": {
"service": "test.automation",
"data_template": {
"some": "not_bat_low {{ trigger.%s }}"
% "}} - {{ trigger.".join(
(
"platform",
"entity_id",
"from_state.state",
"to_state.state",
"for",
)
)
},
},
},
]
},
)
await hass.async_block_till_done()
assert hass.states.get(sensor1.entity_id).state == STATE_ON
assert len(calls) == 0
hass.states.async_set(sensor1.entity_id, STATE_OFF)
await hass.async_block_till_done()
assert len(calls) == 1
assert calls[0].data["some"] == "not_bat_low state - {} - on - off - None".format(
sensor1.entity_id
)
hass.states.async_set(sensor1.entity_id, STATE_ON)
await hass.async_block_till_done()
assert len(calls) == 2
assert calls[1].data["some"] == "bat_low state - {} - off - on - None".format(
sensor1.entity_id
)
async def test_if_state(hass, calls):
"""Test for turn_on and turn_off conditions."""
platform = getattr(hass.components, f"test.{DOMAIN}")
platform.init()
assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}})
sensor1 = platform.ENTITIES["battery"]
assert await async_setup_component(
hass,
automation.DOMAIN,
{
automation.DOMAIN: [
{
"trigger": {"platform": "event", "event_type": "test_event1"},
"condition": [
{
"condition": "device",
"domain": DOMAIN,
"device_id": "",
"entity_id": sensor1.entity_id,
"type": "is_bat_low",
}
],
"action": {
"service": "test.automation",
"data_template": {
"some": "is_on {{ trigger.%s }}"
% "}} - {{ trigger.".join(("platform", "event.event_type"))
},
},
},
{
"trigger": {"platform": "event", "event_type": "test_event2"},
"condition": [
{
"condition": "device",
"domain": DOMAIN,
"device_id": "",
"entity_id": sensor1.entity_id,
"type": "is_not_bat_low",
}
],
"action": {
"service": "test.automation",
"data_template": {
"some": "is_off {{ trigger.%s }}"
% "}} - {{ trigger.".join(("platform", "event.event_type"))
},
},
},
]
},
)
await hass.async_block_till_done()
assert hass.states.get(sensor1.entity_id).state == STATE_ON
assert len(calls) == 0
hass.bus.async_fire("test_event1")
hass.bus.async_fire("test_event2")
await hass.async_block_till_done()
assert len(calls) == 1
assert calls[0].data["some"] == "is_on event - test_event1"
hass.states.async_set(sensor1.entity_id, STATE_OFF)
hass.bus.async_fire("test_event1")
hass.bus.async_fire("test_event2")
await hass.async_block_till_done()
assert len(calls) == 2
assert calls[1].data["some"] == "is_off event - test_event2"

View file

@ -0,0 +1,144 @@
"""The test for binary_sensor device automation."""
import pytest
from homeassistant.components.binary_sensor import DOMAIN, DEVICE_CLASSES
from homeassistant.components.binary_sensor.device_condition import ENTITY_CONDITIONS
from homeassistant.const import STATE_ON, STATE_OFF, CONF_PLATFORM
from homeassistant.setup import async_setup_component
import homeassistant.components.automation as automation
from homeassistant.helpers import device_registry
from tests.common import (
MockConfigEntry,
async_mock_service,
mock_device_registry,
mock_registry,
async_get_device_automations,
)
@pytest.fixture
def device_reg(hass):
"""Return an empty, loaded, registry."""
return mock_device_registry(hass)
@pytest.fixture
def entity_reg(hass):
"""Return an empty, loaded, registry."""
return mock_registry(hass)
@pytest.fixture
def calls(hass):
"""Track calls to a mock serivce."""
return async_mock_service(hass, "test", "automation")
async def test_get_conditions(hass, device_reg, entity_reg):
"""Test we get the expected conditions from a binary_sensor."""
platform = getattr(hass.components, f"test.{DOMAIN}")
platform.init()
config_entry = MockConfigEntry(domain="test", data={})
config_entry.add_to_hass(hass)
device_entry = device_reg.async_get_or_create(
config_entry_id=config_entry.entry_id,
connections={(device_registry.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")},
)
for device_class in DEVICE_CLASSES:
entity_reg.async_get_or_create(
DOMAIN,
"test",
platform.ENTITIES[device_class].unique_id,
device_id=device_entry.id,
)
assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}})
expected_conditions = [
{
"condition": "device",
"domain": DOMAIN,
"type": condition["type"],
"device_id": device_entry.id,
"entity_id": platform.ENTITIES[device_class].entity_id,
}
for device_class in DEVICE_CLASSES
for condition in ENTITY_CONDITIONS[device_class]
]
conditions = await async_get_device_automations(hass, "condition", device_entry.id)
assert conditions == expected_conditions
async def test_if_state(hass, calls):
"""Test for turn_on and turn_off conditions."""
platform = getattr(hass.components, f"test.{DOMAIN}")
platform.init()
assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}})
sensor1 = platform.ENTITIES["battery"]
assert await async_setup_component(
hass,
automation.DOMAIN,
{
automation.DOMAIN: [
{
"trigger": {"platform": "event", "event_type": "test_event1"},
"condition": [
{
"condition": "device",
"domain": DOMAIN,
"device_id": "",
"entity_id": sensor1.entity_id,
"type": "is_bat_low",
}
],
"action": {
"service": "test.automation",
"data_template": {
"some": "is_on {{ trigger.%s }}"
% "}} - {{ trigger.".join(("platform", "event.event_type"))
},
},
},
{
"trigger": {"platform": "event", "event_type": "test_event2"},
"condition": [
{
"condition": "device",
"domain": DOMAIN,
"device_id": "",
"entity_id": sensor1.entity_id,
"type": "is_not_bat_low",
}
],
"action": {
"service": "test.automation",
"data_template": {
"some": "is_off {{ trigger.%s }}"
% "}} - {{ trigger.".join(("platform", "event.event_type"))
},
},
},
]
},
)
await hass.async_block_till_done()
assert hass.states.get(sensor1.entity_id).state == STATE_ON
assert len(calls) == 0
hass.bus.async_fire("test_event1")
hass.bus.async_fire("test_event2")
await hass.async_block_till_done()
assert len(calls) == 1
assert calls[0].data["some"] == "is_on event - test_event1"
hass.states.async_set(sensor1.entity_id, STATE_OFF)
hass.bus.async_fire("test_event1")
hass.bus.async_fire("test_event2")
await hass.async_block_till_done()
assert len(calls) == 2
assert calls[1].data["some"] == "is_off event - test_event2"

View file

@ -0,0 +1,154 @@
"""The test for binary_sensor device automation."""
import pytest
from homeassistant.components.binary_sensor import DOMAIN, DEVICE_CLASSES
from homeassistant.components.binary_sensor.device_trigger import ENTITY_TRIGGERS
from homeassistant.const import STATE_ON, STATE_OFF, CONF_PLATFORM
from homeassistant.setup import async_setup_component
import homeassistant.components.automation as automation
from homeassistant.helpers import device_registry
from tests.common import (
MockConfigEntry,
async_mock_service,
mock_device_registry,
mock_registry,
async_get_device_automations,
)
@pytest.fixture
def device_reg(hass):
"""Return an empty, loaded, registry."""
return mock_device_registry(hass)
@pytest.fixture
def entity_reg(hass):
"""Return an empty, loaded, registry."""
return mock_registry(hass)
@pytest.fixture
def calls(hass):
"""Track calls to a mock serivce."""
return async_mock_service(hass, "test", "automation")
async def test_get_triggers(hass, device_reg, entity_reg):
"""Test we get the expected triggers from a binary_sensor."""
platform = getattr(hass.components, f"test.{DOMAIN}")
platform.init()
config_entry = MockConfigEntry(domain="test", data={})
config_entry.add_to_hass(hass)
device_entry = device_reg.async_get_or_create(
config_entry_id=config_entry.entry_id,
connections={(device_registry.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")},
)
for device_class in DEVICE_CLASSES:
entity_reg.async_get_or_create(
DOMAIN,
"test",
platform.ENTITIES[device_class].unique_id,
device_id=device_entry.id,
)
assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}})
expected_triggers = [
{
"platform": "device",
"domain": DOMAIN,
"type": trigger["type"],
"device_id": device_entry.id,
"entity_id": platform.ENTITIES[device_class].entity_id,
}
for device_class in DEVICE_CLASSES
for trigger in ENTITY_TRIGGERS[device_class]
]
triggers = await async_get_device_automations(hass, "trigger", device_entry.id)
assert triggers == expected_triggers
async def test_if_fires_on_state_change(hass, calls):
"""Test for on and off triggers firing."""
platform = getattr(hass.components, f"test.{DOMAIN}")
platform.init()
assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}})
sensor1 = platform.ENTITIES["battery"]
assert await async_setup_component(
hass,
automation.DOMAIN,
{
automation.DOMAIN: [
{
"trigger": {
"platform": "device",
"domain": DOMAIN,
"device_id": "",
"entity_id": sensor1.entity_id,
"type": "bat_low",
},
"action": {
"service": "test.automation",
"data_template": {
"some": "bat_low {{ trigger.%s }}"
% "}} - {{ trigger.".join(
(
"platform",
"entity_id",
"from_state.state",
"to_state.state",
"for",
)
)
},
},
},
{
"trigger": {
"platform": "device",
"domain": DOMAIN,
"device_id": "",
"entity_id": sensor1.entity_id,
"type": "not_bat_low",
},
"action": {
"service": "test.automation",
"data_template": {
"some": "not_bat_low {{ trigger.%s }}"
% "}} - {{ trigger.".join(
(
"platform",
"entity_id",
"from_state.state",
"to_state.state",
"for",
)
)
},
},
},
]
},
)
await hass.async_block_till_done()
assert hass.states.get(sensor1.entity_id).state == STATE_ON
assert len(calls) == 0
hass.states.async_set(sensor1.entity_id, STATE_OFF)
await hass.async_block_till_done()
assert len(calls) == 1
assert calls[0].data["some"] == "not_bat_low device - {} - on - off - None".format(
sensor1.entity_id
)
hass.states.async_set(sensor1.entity_id, STATE_ON)
await hass.async_block_till_done()
assert len(calls) == 2
assert calls[1].data["some"] == "bat_low device - {} - off - on - None".format(
sensor1.entity_id
)

View file

@ -3,9 +3,9 @@ from asynctest import patch
from homeassistant import config_entries from homeassistant import config_entries
from homeassistant.components import deconz from homeassistant.components import deconz
from homeassistant.components.device_automation import ( from homeassistant.components.deconz import device_trigger
_async_get_device_automations as async_get_device_automations,
) from tests.common import async_get_device_automations
BRIDGEID = "0123456789" BRIDGEID = "0123456789"
@ -49,16 +49,6 @@ DECONZ_SENSOR = {
DECONZ_WEB_REQUEST = {"config": DECONZ_CONFIG, "sensors": DECONZ_SENSOR} DECONZ_WEB_REQUEST = {"config": DECONZ_CONFIG, "sensors": DECONZ_SENSOR}
def _same_lists(a, b):
if len(a) != len(b):
return False
for d in a:
if d not in b:
return False
return True
async def setup_deconz(hass, options): async def setup_deconz(hass, options):
"""Create the deCONZ gateway.""" """Create the deCONZ gateway."""
config_entry = config_entries.ConfigEntry( config_entry = config_entries.ConfigEntry(
@ -88,51 +78,51 @@ async def test_get_triggers(hass):
"""Test triggers work.""" """Test triggers work."""
gateway = await setup_deconz(hass, options={}) gateway = await setup_deconz(hass, options={})
device_id = gateway.events[0].device_id device_id = gateway.events[0].device_id
triggers = await async_get_device_automations(hass, "async_get_triggers", device_id) triggers = await async_get_device_automations(hass, "trigger", device_id)
expected_triggers = [ expected_triggers = [
{ {
"device_id": device_id, "device_id": device_id,
"domain": "deconz", "domain": "deconz",
"platform": "device", "platform": "device",
"type": deconz.device_automation.CONF_SHORT_PRESS, "type": device_trigger.CONF_SHORT_PRESS,
"subtype": deconz.device_automation.CONF_TURN_ON, "subtype": device_trigger.CONF_TURN_ON,
}, },
{ {
"device_id": device_id, "device_id": device_id,
"domain": "deconz", "domain": "deconz",
"platform": "device", "platform": "device",
"type": deconz.device_automation.CONF_LONG_PRESS, "type": device_trigger.CONF_LONG_PRESS,
"subtype": deconz.device_automation.CONF_TURN_ON, "subtype": device_trigger.CONF_TURN_ON,
}, },
{ {
"device_id": device_id, "device_id": device_id,
"domain": "deconz", "domain": "deconz",
"platform": "device", "platform": "device",
"type": deconz.device_automation.CONF_LONG_RELEASE, "type": device_trigger.CONF_LONG_RELEASE,
"subtype": deconz.device_automation.CONF_TURN_ON, "subtype": device_trigger.CONF_TURN_ON,
}, },
{ {
"device_id": device_id, "device_id": device_id,
"domain": "deconz", "domain": "deconz",
"platform": "device", "platform": "device",
"type": deconz.device_automation.CONF_SHORT_PRESS, "type": device_trigger.CONF_SHORT_PRESS,
"subtype": deconz.device_automation.CONF_TURN_OFF, "subtype": device_trigger.CONF_TURN_OFF,
}, },
{ {
"device_id": device_id, "device_id": device_id,
"domain": "deconz", "domain": "deconz",
"platform": "device", "platform": "device",
"type": deconz.device_automation.CONF_LONG_PRESS, "type": device_trigger.CONF_LONG_PRESS,
"subtype": deconz.device_automation.CONF_TURN_OFF, "subtype": device_trigger.CONF_TURN_OFF,
}, },
{ {
"device_id": device_id, "device_id": device_id,
"domain": "deconz", "domain": "deconz",
"platform": "device", "platform": "device",
"type": deconz.device_automation.CONF_LONG_RELEASE, "type": device_trigger.CONF_LONG_RELEASE,
"subtype": deconz.device_automation.CONF_TURN_OFF, "subtype": device_trigger.CONF_TURN_OFF,
}, },
] ]
assert _same_lists(triggers, expected_triggers) assert triggers == expected_triggers

View file

@ -0,0 +1,140 @@
"""The test for light device automation."""
import pytest
from homeassistant.components.light import DOMAIN
from homeassistant.const import STATE_ON, STATE_OFF, CONF_PLATFORM
from homeassistant.setup import async_setup_component
import homeassistant.components.automation as automation
from homeassistant.helpers import device_registry
from tests.common import (
MockConfigEntry,
async_mock_service,
mock_device_registry,
mock_registry,
async_get_device_automations,
)
@pytest.fixture
def device_reg(hass):
"""Return an empty, loaded, registry."""
return mock_device_registry(hass)
@pytest.fixture
def entity_reg(hass):
"""Return an empty, loaded, registry."""
return mock_registry(hass)
@pytest.fixture
def calls(hass):
"""Track calls to a mock serivce."""
return async_mock_service(hass, "test", "automation")
async def test_get_actions(hass, device_reg, entity_reg):
"""Test we get the expected actions from a light."""
config_entry = MockConfigEntry(domain="test", data={})
config_entry.add_to_hass(hass)
device_entry = device_reg.async_get_or_create(
config_entry_id=config_entry.entry_id,
connections={(device_registry.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")},
)
entity_reg.async_get_or_create(DOMAIN, "test", "5678", device_id=device_entry.id)
expected_actions = [
{
"domain": DOMAIN,
"type": "turn_off",
"device_id": device_entry.id,
"entity_id": f"{DOMAIN}.test_5678",
},
{
"domain": DOMAIN,
"type": "turn_on",
"device_id": device_entry.id,
"entity_id": f"{DOMAIN}.test_5678",
},
{
"domain": DOMAIN,
"type": "toggle",
"device_id": device_entry.id,
"entity_id": f"{DOMAIN}.test_5678",
},
]
actions = await async_get_device_automations(hass, "action", device_entry.id)
assert actions == expected_actions
async def test_action(hass, calls):
"""Test for turn_on and turn_off actions."""
platform = getattr(hass.components, f"test.{DOMAIN}")
platform.init()
assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}})
ent1, ent2, ent3 = platform.ENTITIES
assert await async_setup_component(
hass,
automation.DOMAIN,
{
automation.DOMAIN: [
{
"trigger": {"platform": "event", "event_type": "test_event1"},
"action": {
"domain": DOMAIN,
"device_id": "",
"entity_id": ent1.entity_id,
"type": "turn_off",
},
},
{
"trigger": {"platform": "event", "event_type": "test_event2"},
"action": {
"domain": DOMAIN,
"device_id": "",
"entity_id": ent1.entity_id,
"type": "turn_on",
},
},
{
"trigger": {"platform": "event", "event_type": "test_event3"},
"action": {
"domain": DOMAIN,
"device_id": "",
"entity_id": ent1.entity_id,
"type": "toggle",
},
},
]
},
)
await hass.async_block_till_done()
assert hass.states.get(ent1.entity_id).state == STATE_ON
assert len(calls) == 0
hass.bus.async_fire("test_event1")
await hass.async_block_till_done()
assert hass.states.get(ent1.entity_id).state == STATE_OFF
hass.bus.async_fire("test_event1")
await hass.async_block_till_done()
assert hass.states.get(ent1.entity_id).state == STATE_OFF
hass.bus.async_fire("test_event2")
await hass.async_block_till_done()
assert hass.states.get(ent1.entity_id).state == STATE_ON
hass.bus.async_fire("test_event2")
await hass.async_block_till_done()
assert hass.states.get(ent1.entity_id).state == STATE_ON
hass.bus.async_fire("test_event3")
await hass.async_block_till_done()
assert hass.states.get(ent1.entity_id).state == STATE_OFF
hass.bus.async_fire("test_event3")
await hass.async_block_till_done()
assert hass.states.get(ent1.entity_id).state == STATE_ON

View file

@ -1,373 +0,0 @@
"""The test for light device automation."""
import pytest
from homeassistant.components.light import DOMAIN
from homeassistant.const import STATE_ON, STATE_OFF, CONF_PLATFORM
from homeassistant.setup import async_setup_component
import homeassistant.components.automation as automation
from homeassistant.components.device_automation import (
_async_get_device_automations as async_get_device_automations,
)
from homeassistant.helpers import device_registry
from tests.common import (
MockConfigEntry,
async_mock_service,
mock_device_registry,
mock_registry,
)
@pytest.fixture
def device_reg(hass):
"""Return an empty, loaded, registry."""
return mock_device_registry(hass)
@pytest.fixture
def entity_reg(hass):
"""Return an empty, loaded, registry."""
return mock_registry(hass)
@pytest.fixture
def calls(hass):
"""Track calls to a mock serivce."""
return async_mock_service(hass, "test", "automation")
def _same_lists(a, b):
if len(a) != len(b):
return False
for d in a:
if d not in b:
return False
return True
async def test_get_actions(hass, device_reg, entity_reg):
"""Test we get the expected actions from a light."""
config_entry = MockConfigEntry(domain="test", data={})
config_entry.add_to_hass(hass)
device_entry = device_reg.async_get_or_create(
config_entry_id=config_entry.entry_id,
connections={(device_registry.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")},
)
entity_reg.async_get_or_create(DOMAIN, "test", "5678", device_id=device_entry.id)
expected_actions = [
{
"domain": DOMAIN,
"type": "turn_off",
"device_id": device_entry.id,
"entity_id": f"{DOMAIN}.test_5678",
},
{
"domain": DOMAIN,
"type": "turn_on",
"device_id": device_entry.id,
"entity_id": f"{DOMAIN}.test_5678",
},
{
"domain": DOMAIN,
"type": "toggle",
"device_id": device_entry.id,
"entity_id": f"{DOMAIN}.test_5678",
},
]
actions = await async_get_device_automations(
hass, "async_get_actions", device_entry.id
)
assert _same_lists(actions, expected_actions)
async def test_get_conditions(hass, device_reg, entity_reg):
"""Test we get the expected conditions from a light."""
config_entry = MockConfigEntry(domain="test", data={})
config_entry.add_to_hass(hass)
device_entry = device_reg.async_get_or_create(
config_entry_id=config_entry.entry_id,
connections={(device_registry.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")},
)
entity_reg.async_get_or_create(DOMAIN, "test", "5678", device_id=device_entry.id)
expected_conditions = [
{
"condition": "device",
"domain": DOMAIN,
"type": "is_off",
"device_id": device_entry.id,
"entity_id": f"{DOMAIN}.test_5678",
},
{
"condition": "device",
"domain": DOMAIN,
"type": "is_on",
"device_id": device_entry.id,
"entity_id": f"{DOMAIN}.test_5678",
},
]
conditions = await async_get_device_automations(
hass, "async_get_conditions", device_entry.id
)
assert _same_lists(conditions, expected_conditions)
async def test_get_triggers(hass, device_reg, entity_reg):
"""Test we get the expected triggers from a light."""
config_entry = MockConfigEntry(domain="test", data={})
config_entry.add_to_hass(hass)
device_entry = device_reg.async_get_or_create(
config_entry_id=config_entry.entry_id,
connections={(device_registry.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")},
)
entity_reg.async_get_or_create(DOMAIN, "test", "5678", device_id=device_entry.id)
expected_triggers = [
{
"platform": "device",
"domain": DOMAIN,
"type": "turned_off",
"device_id": device_entry.id,
"entity_id": f"{DOMAIN}.test_5678",
},
{
"platform": "device",
"domain": DOMAIN,
"type": "turned_on",
"device_id": device_entry.id,
"entity_id": f"{DOMAIN}.test_5678",
},
]
triggers = await async_get_device_automations(
hass, "async_get_triggers", device_entry.id
)
assert _same_lists(triggers, expected_triggers)
async def test_if_fires_on_state_change(hass, calls):
"""Test for turn_on and turn_off triggers firing."""
platform = getattr(hass.components, f"test.{DOMAIN}")
platform.init()
assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}})
ent1, ent2, ent3 = platform.ENTITIES
assert await async_setup_component(
hass,
automation.DOMAIN,
{
automation.DOMAIN: [
{
"trigger": {
"platform": "device",
"domain": DOMAIN,
"device_id": "",
"entity_id": ent1.entity_id,
"type": "turned_on",
},
"action": {
"service": "test.automation",
"data_template": {
"some": "turn_on {{ trigger.%s }}"
% "}} - {{ trigger.".join(
(
"platform",
"entity_id",
"from_state.state",
"to_state.state",
"for",
)
)
},
},
},
{
"trigger": {
"platform": "device",
"domain": DOMAIN,
"device_id": "",
"entity_id": ent1.entity_id,
"type": "turned_off",
},
"action": {
"service": "test.automation",
"data_template": {
"some": "turn_off {{ trigger.%s }}"
% "}} - {{ trigger.".join(
(
"platform",
"entity_id",
"from_state.state",
"to_state.state",
"for",
)
)
},
},
},
]
},
)
await hass.async_block_till_done()
assert hass.states.get(ent1.entity_id).state == STATE_ON
assert len(calls) == 0
hass.states.async_set(ent1.entity_id, STATE_OFF)
await hass.async_block_till_done()
assert len(calls) == 1
assert calls[0].data["some"] == "turn_off state - {} - on - off - None".format(
ent1.entity_id
)
hass.states.async_set(ent1.entity_id, STATE_ON)
await hass.async_block_till_done()
assert len(calls) == 2
assert calls[1].data["some"] == "turn_on state - {} - off - on - None".format(
ent1.entity_id
)
async def test_if_state(hass, calls):
"""Test for turn_on and turn_off conditions."""
platform = getattr(hass.components, f"test.{DOMAIN}")
platform.init()
assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}})
ent1, ent2, ent3 = platform.ENTITIES
assert await async_setup_component(
hass,
automation.DOMAIN,
{
automation.DOMAIN: [
{
"trigger": {"platform": "event", "event_type": "test_event1"},
"condition": [
{
"condition": "device",
"domain": DOMAIN,
"device_id": "",
"entity_id": ent1.entity_id,
"type": "is_on",
}
],
"action": {
"service": "test.automation",
"data_template": {
"some": "is_on {{ trigger.%s }}"
% "}} - {{ trigger.".join(("platform", "event.event_type"))
},
},
},
{
"trigger": {"platform": "event", "event_type": "test_event2"},
"condition": [
{
"condition": "device",
"domain": DOMAIN,
"device_id": "",
"entity_id": ent1.entity_id,
"type": "is_off",
}
],
"action": {
"service": "test.automation",
"data_template": {
"some": "is_off {{ trigger.%s }}"
% "}} - {{ trigger.".join(("platform", "event.event_type"))
},
},
},
]
},
)
await hass.async_block_till_done()
assert hass.states.get(ent1.entity_id).state == STATE_ON
assert len(calls) == 0
hass.bus.async_fire("test_event1")
hass.bus.async_fire("test_event2")
await hass.async_block_till_done()
assert len(calls) == 1
assert calls[0].data["some"] == "is_on event - test_event1"
hass.states.async_set(ent1.entity_id, STATE_OFF)
hass.bus.async_fire("test_event1")
hass.bus.async_fire("test_event2")
await hass.async_block_till_done()
assert len(calls) == 2
assert calls[1].data["some"] == "is_off event - test_event2"
async def test_action(hass, calls):
"""Test for turn_on and turn_off actions."""
platform = getattr(hass.components, f"test.{DOMAIN}")
platform.init()
assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}})
ent1, ent2, ent3 = platform.ENTITIES
assert await async_setup_component(
hass,
automation.DOMAIN,
{
automation.DOMAIN: [
{
"trigger": {"platform": "event", "event_type": "test_event1"},
"action": {
"domain": DOMAIN,
"device_id": "",
"entity_id": ent1.entity_id,
"type": "turn_off",
},
},
{
"trigger": {"platform": "event", "event_type": "test_event2"},
"action": {
"domain": DOMAIN,
"device_id": "",
"entity_id": ent1.entity_id,
"type": "turn_on",
},
},
{
"trigger": {"platform": "event", "event_type": "test_event3"},
"action": {
"domain": DOMAIN,
"device_id": "",
"entity_id": ent1.entity_id,
"type": "toggle",
},
},
]
},
)
await hass.async_block_till_done()
assert hass.states.get(ent1.entity_id).state == STATE_ON
assert len(calls) == 0
hass.bus.async_fire("test_event1")
await hass.async_block_till_done()
assert hass.states.get(ent1.entity_id).state == STATE_OFF
hass.bus.async_fire("test_event1")
await hass.async_block_till_done()
assert hass.states.get(ent1.entity_id).state == STATE_OFF
hass.bus.async_fire("test_event2")
await hass.async_block_till_done()
assert hass.states.get(ent1.entity_id).state == STATE_ON
hass.bus.async_fire("test_event2")
await hass.async_block_till_done()
assert hass.states.get(ent1.entity_id).state == STATE_ON
hass.bus.async_fire("test_event3")
await hass.async_block_till_done()
assert hass.states.get(ent1.entity_id).state == STATE_OFF
hass.bus.async_fire("test_event3")
await hass.async_block_till_done()
assert hass.states.get(ent1.entity_id).state == STATE_ON

View file

@ -0,0 +1,136 @@
"""The test for light device automation."""
import pytest
from homeassistant.components.light import DOMAIN
from homeassistant.const import STATE_ON, STATE_OFF, CONF_PLATFORM
from homeassistant.setup import async_setup_component
import homeassistant.components.automation as automation
from homeassistant.helpers import device_registry
from tests.common import (
MockConfigEntry,
async_mock_service,
mock_device_registry,
mock_registry,
async_get_device_automations,
)
@pytest.fixture
def device_reg(hass):
"""Return an empty, loaded, registry."""
return mock_device_registry(hass)
@pytest.fixture
def entity_reg(hass):
"""Return an empty, loaded, registry."""
return mock_registry(hass)
@pytest.fixture
def calls(hass):
"""Track calls to a mock serivce."""
return async_mock_service(hass, "test", "automation")
async def test_get_conditions(hass, device_reg, entity_reg):
"""Test we get the expected conditions from a light."""
config_entry = MockConfigEntry(domain="test", data={})
config_entry.add_to_hass(hass)
device_entry = device_reg.async_get_or_create(
config_entry_id=config_entry.entry_id,
connections={(device_registry.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")},
)
entity_reg.async_get_or_create(DOMAIN, "test", "5678", device_id=device_entry.id)
expected_conditions = [
{
"condition": "device",
"domain": DOMAIN,
"type": "is_off",
"device_id": device_entry.id,
"entity_id": f"{DOMAIN}.test_5678",
},
{
"condition": "device",
"domain": DOMAIN,
"type": "is_on",
"device_id": device_entry.id,
"entity_id": f"{DOMAIN}.test_5678",
},
]
conditions = await async_get_device_automations(hass, "condition", device_entry.id)
assert conditions == expected_conditions
async def test_if_state(hass, calls):
"""Test for turn_on and turn_off conditions."""
platform = getattr(hass.components, f"test.{DOMAIN}")
platform.init()
assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}})
ent1, ent2, ent3 = platform.ENTITIES
assert await async_setup_component(
hass,
automation.DOMAIN,
{
automation.DOMAIN: [
{
"trigger": {"platform": "event", "event_type": "test_event1"},
"condition": [
{
"condition": "device",
"domain": DOMAIN,
"device_id": "",
"entity_id": ent1.entity_id,
"type": "is_on",
}
],
"action": {
"service": "test.automation",
"data_template": {
"some": "is_on {{ trigger.%s }}"
% "}} - {{ trigger.".join(("platform", "event.event_type"))
},
},
},
{
"trigger": {"platform": "event", "event_type": "test_event2"},
"condition": [
{
"condition": "device",
"domain": DOMAIN,
"device_id": "",
"entity_id": ent1.entity_id,
"type": "is_off",
}
],
"action": {
"service": "test.automation",
"data_template": {
"some": "is_off {{ trigger.%s }}"
% "}} - {{ trigger.".join(("platform", "event.event_type"))
},
},
},
]
},
)
await hass.async_block_till_done()
assert hass.states.get(ent1.entity_id).state == STATE_ON
assert len(calls) == 0
hass.bus.async_fire("test_event1")
hass.bus.async_fire("test_event2")
await hass.async_block_till_done()
assert len(calls) == 1
assert calls[0].data["some"] == "is_on event - test_event1"
hass.states.async_set(ent1.entity_id, STATE_OFF)
hass.bus.async_fire("test_event1")
hass.bus.async_fire("test_event2")
await hass.async_block_till_done()
assert len(calls) == 2
assert calls[1].data["some"] == "is_off event - test_event2"

View file

@ -0,0 +1,147 @@
"""The test for light device automation."""
import pytest
from homeassistant.components.light import DOMAIN
from homeassistant.const import STATE_ON, STATE_OFF, CONF_PLATFORM
from homeassistant.setup import async_setup_component
import homeassistant.components.automation as automation
from homeassistant.helpers import device_registry
from tests.common import (
MockConfigEntry,
async_mock_service,
mock_device_registry,
mock_registry,
async_get_device_automations,
)
@pytest.fixture
def device_reg(hass):
"""Return an empty, loaded, registry."""
return mock_device_registry(hass)
@pytest.fixture
def entity_reg(hass):
"""Return an empty, loaded, registry."""
return mock_registry(hass)
@pytest.fixture
def calls(hass):
"""Track calls to a mock serivce."""
return async_mock_service(hass, "test", "automation")
async def test_get_triggers(hass, device_reg, entity_reg):
"""Test we get the expected triggers from a light."""
config_entry = MockConfigEntry(domain="test", data={})
config_entry.add_to_hass(hass)
device_entry = device_reg.async_get_or_create(
config_entry_id=config_entry.entry_id,
connections={(device_registry.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")},
)
entity_reg.async_get_or_create(DOMAIN, "test", "5678", device_id=device_entry.id)
expected_triggers = [
{
"platform": "device",
"domain": DOMAIN,
"type": "turned_off",
"device_id": device_entry.id,
"entity_id": f"{DOMAIN}.test_5678",
},
{
"platform": "device",
"domain": DOMAIN,
"type": "turned_on",
"device_id": device_entry.id,
"entity_id": f"{DOMAIN}.test_5678",
},
]
triggers = await async_get_device_automations(hass, "trigger", device_entry.id)
assert triggers == expected_triggers
async def test_if_fires_on_state_change(hass, calls):
"""Test for turn_on and turn_off triggers firing."""
platform = getattr(hass.components, f"test.{DOMAIN}")
platform.init()
assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}})
ent1, ent2, ent3 = platform.ENTITIES
assert await async_setup_component(
hass,
automation.DOMAIN,
{
automation.DOMAIN: [
{
"trigger": {
"platform": "device",
"domain": DOMAIN,
"device_id": "",
"entity_id": ent1.entity_id,
"type": "turned_on",
},
"action": {
"service": "test.automation",
"data_template": {
"some": "turn_on {{ trigger.%s }}"
% "}} - {{ trigger.".join(
(
"platform",
"entity_id",
"from_state.state",
"to_state.state",
"for",
)
)
},
},
},
{
"trigger": {
"platform": "device",
"domain": DOMAIN,
"device_id": "",
"entity_id": ent1.entity_id,
"type": "turned_off",
},
"action": {
"service": "test.automation",
"data_template": {
"some": "turn_off {{ trigger.%s }}"
% "}} - {{ trigger.".join(
(
"platform",
"entity_id",
"from_state.state",
"to_state.state",
"for",
)
)
},
},
},
]
},
)
await hass.async_block_till_done()
assert hass.states.get(ent1.entity_id).state == STATE_ON
assert len(calls) == 0
hass.states.async_set(ent1.entity_id, STATE_OFF)
await hass.async_block_till_done()
assert len(calls) == 1
assert calls[0].data["some"] == "turn_off device - {} - on - off - None".format(
ent1.entity_id
)
hass.states.async_set(ent1.entity_id, STATE_ON)
await hass.async_block_till_done()
assert len(calls) == 2
assert calls[1].data["some"] == "turn_on device - {} - off - on - None".format(
ent1.entity_id
)

View file

@ -0,0 +1,142 @@
"""The test for switch device automation."""
import pytest
from homeassistant.components.switch import DOMAIN
from homeassistant.const import STATE_ON, STATE_OFF, CONF_PLATFORM
from homeassistant.setup import async_setup_component
import homeassistant.components.automation as automation
from homeassistant.components.device_automation import (
_async_get_device_automations as async_get_device_automations,
)
from homeassistant.helpers import device_registry
from tests.common import (
MockConfigEntry,
async_mock_service,
mock_device_registry,
mock_registry,
)
@pytest.fixture
def device_reg(hass):
"""Return an empty, loaded, registry."""
return mock_device_registry(hass)
@pytest.fixture
def entity_reg(hass):
"""Return an empty, loaded, registry."""
return mock_registry(hass)
@pytest.fixture
def calls(hass):
"""Track calls to a mock serivce."""
return async_mock_service(hass, "test", "automation")
async def test_get_actions(hass, device_reg, entity_reg):
"""Test we get the expected actions from a switch."""
config_entry = MockConfigEntry(domain="test", data={})
config_entry.add_to_hass(hass)
device_entry = device_reg.async_get_or_create(
config_entry_id=config_entry.entry_id,
connections={(device_registry.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")},
)
entity_reg.async_get_or_create(DOMAIN, "test", "5678", device_id=device_entry.id)
expected_actions = [
{
"domain": DOMAIN,
"type": "turn_off",
"device_id": device_entry.id,
"entity_id": f"{DOMAIN}.test_5678",
},
{
"domain": DOMAIN,
"type": "turn_on",
"device_id": device_entry.id,
"entity_id": f"{DOMAIN}.test_5678",
},
{
"domain": DOMAIN,
"type": "toggle",
"device_id": device_entry.id,
"entity_id": f"{DOMAIN}.test_5678",
},
]
actions = await async_get_device_automations(hass, "action", device_entry.id)
assert actions == expected_actions
async def test_action(hass, calls):
"""Test for turn_on and turn_off actions."""
platform = getattr(hass.components, f"test.{DOMAIN}")
platform.init()
assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}})
ent1, ent2, ent3 = platform.ENTITIES
assert await async_setup_component(
hass,
automation.DOMAIN,
{
automation.DOMAIN: [
{
"trigger": {"platform": "event", "event_type": "test_event1"},
"action": {
"domain": DOMAIN,
"device_id": "",
"entity_id": ent1.entity_id,
"type": "turn_off",
},
},
{
"trigger": {"platform": "event", "event_type": "test_event2"},
"action": {
"domain": DOMAIN,
"device_id": "",
"entity_id": ent1.entity_id,
"type": "turn_on",
},
},
{
"trigger": {"platform": "event", "event_type": "test_event3"},
"action": {
"domain": DOMAIN,
"device_id": "",
"entity_id": ent1.entity_id,
"type": "toggle",
},
},
]
},
)
await hass.async_block_till_done()
assert hass.states.get(ent1.entity_id).state == STATE_ON
assert len(calls) == 0
hass.bus.async_fire("test_event1")
await hass.async_block_till_done()
assert hass.states.get(ent1.entity_id).state == STATE_OFF
hass.bus.async_fire("test_event1")
await hass.async_block_till_done()
assert hass.states.get(ent1.entity_id).state == STATE_OFF
hass.bus.async_fire("test_event2")
await hass.async_block_till_done()
assert hass.states.get(ent1.entity_id).state == STATE_ON
hass.bus.async_fire("test_event2")
await hass.async_block_till_done()
assert hass.states.get(ent1.entity_id).state == STATE_ON
hass.bus.async_fire("test_event3")
await hass.async_block_till_done()
assert hass.states.get(ent1.entity_id).state == STATE_OFF
hass.bus.async_fire("test_event3")
await hass.async_block_till_done()
assert hass.states.get(ent1.entity_id).state == STATE_ON

View file

@ -1,373 +0,0 @@
"""The test for switch device automation."""
import pytest
from homeassistant.components.switch import DOMAIN
from homeassistant.const import STATE_ON, STATE_OFF, CONF_PLATFORM
from homeassistant.setup import async_setup_component
import homeassistant.components.automation as automation
from homeassistant.components.device_automation import (
_async_get_device_automations as async_get_device_automations,
)
from homeassistant.helpers import device_registry
from tests.common import (
MockConfigEntry,
async_mock_service,
mock_device_registry,
mock_registry,
)
@pytest.fixture
def device_reg(hass):
"""Return an empty, loaded, registry."""
return mock_device_registry(hass)
@pytest.fixture
def entity_reg(hass):
"""Return an empty, loaded, registry."""
return mock_registry(hass)
@pytest.fixture
def calls(hass):
"""Track calls to a mock serivce."""
return async_mock_service(hass, "test", "automation")
def _same_lists(a, b):
if len(a) != len(b):
return False
for d in a:
if d not in b:
return False
return True
async def test_get_actions(hass, device_reg, entity_reg):
"""Test we get the expected actions from a switch."""
config_entry = MockConfigEntry(domain="test", data={})
config_entry.add_to_hass(hass)
device_entry = device_reg.async_get_or_create(
config_entry_id=config_entry.entry_id,
connections={(device_registry.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")},
)
entity_reg.async_get_or_create(DOMAIN, "test", "5678", device_id=device_entry.id)
expected_actions = [
{
"domain": DOMAIN,
"type": "turn_off",
"device_id": device_entry.id,
"entity_id": f"{DOMAIN}.test_5678",
},
{
"domain": DOMAIN,
"type": "turn_on",
"device_id": device_entry.id,
"entity_id": f"{DOMAIN}.test_5678",
},
{
"domain": DOMAIN,
"type": "toggle",
"device_id": device_entry.id,
"entity_id": f"{DOMAIN}.test_5678",
},
]
actions = await async_get_device_automations(
hass, "async_get_actions", device_entry.id
)
assert _same_lists(actions, expected_actions)
async def test_get_conditions(hass, device_reg, entity_reg):
"""Test we get the expected conditions from a switch."""
config_entry = MockConfigEntry(domain="test", data={})
config_entry.add_to_hass(hass)
device_entry = device_reg.async_get_or_create(
config_entry_id=config_entry.entry_id,
connections={(device_registry.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")},
)
entity_reg.async_get_or_create(DOMAIN, "test", "5678", device_id=device_entry.id)
expected_conditions = [
{
"condition": "device",
"domain": DOMAIN,
"type": "is_off",
"device_id": device_entry.id,
"entity_id": f"{DOMAIN}.test_5678",
},
{
"condition": "device",
"domain": DOMAIN,
"type": "is_on",
"device_id": device_entry.id,
"entity_id": f"{DOMAIN}.test_5678",
},
]
conditions = await async_get_device_automations(
hass, "async_get_conditions", device_entry.id
)
assert _same_lists(conditions, expected_conditions)
async def test_get_triggers(hass, device_reg, entity_reg):
"""Test we get the expected triggers from a switch."""
config_entry = MockConfigEntry(domain="test", data={})
config_entry.add_to_hass(hass)
device_entry = device_reg.async_get_or_create(
config_entry_id=config_entry.entry_id,
connections={(device_registry.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")},
)
entity_reg.async_get_or_create(DOMAIN, "test", "5678", device_id=device_entry.id)
expected_triggers = [
{
"platform": "device",
"domain": DOMAIN,
"type": "turned_off",
"device_id": device_entry.id,
"entity_id": f"{DOMAIN}.test_5678",
},
{
"platform": "device",
"domain": DOMAIN,
"type": "turned_on",
"device_id": device_entry.id,
"entity_id": f"{DOMAIN}.test_5678",
},
]
triggers = await async_get_device_automations(
hass, "async_get_triggers", device_entry.id
)
assert _same_lists(triggers, expected_triggers)
async def test_if_fires_on_state_change(hass, calls):
"""Test for turn_on and turn_off triggers firing."""
platform = getattr(hass.components, f"test.{DOMAIN}")
platform.init()
assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}})
ent1, ent2, ent3 = platform.ENTITIES
assert await async_setup_component(
hass,
automation.DOMAIN,
{
automation.DOMAIN: [
{
"trigger": {
"platform": "device",
"domain": DOMAIN,
"device_id": "",
"entity_id": ent1.entity_id,
"type": "turned_on",
},
"action": {
"service": "test.automation",
"data_template": {
"some": "turn_on {{ trigger.%s }}"
% "}} - {{ trigger.".join(
(
"platform",
"entity_id",
"from_state.state",
"to_state.state",
"for",
)
)
},
},
},
{
"trigger": {
"platform": "device",
"domain": DOMAIN,
"device_id": "",
"entity_id": ent1.entity_id,
"type": "turned_off",
},
"action": {
"service": "test.automation",
"data_template": {
"some": "turn_off {{ trigger.%s }}"
% "}} - {{ trigger.".join(
(
"platform",
"entity_id",
"from_state.state",
"to_state.state",
"for",
)
)
},
},
},
]
},
)
await hass.async_block_till_done()
assert hass.states.get(ent1.entity_id).state == STATE_ON
assert len(calls) == 0
hass.states.async_set(ent1.entity_id, STATE_OFF)
await hass.async_block_till_done()
assert len(calls) == 1
assert calls[0].data["some"] == "turn_off state - {} - on - off - None".format(
ent1.entity_id
)
hass.states.async_set(ent1.entity_id, STATE_ON)
await hass.async_block_till_done()
assert len(calls) == 2
assert calls[1].data["some"] == "turn_on state - {} - off - on - None".format(
ent1.entity_id
)
async def test_if_state(hass, calls):
"""Test for turn_on and turn_off conditions."""
platform = getattr(hass.components, f"test.{DOMAIN}")
platform.init()
assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}})
ent1, ent2, ent3 = platform.ENTITIES
assert await async_setup_component(
hass,
automation.DOMAIN,
{
automation.DOMAIN: [
{
"trigger": {"platform": "event", "event_type": "test_event1"},
"condition": [
{
"condition": "device",
"domain": DOMAIN,
"device_id": "",
"entity_id": ent1.entity_id,
"type": "is_on",
}
],
"action": {
"service": "test.automation",
"data_template": {
"some": "is_on {{ trigger.%s }}"
% "}} - {{ trigger.".join(("platform", "event.event_type"))
},
},
},
{
"trigger": {"platform": "event", "event_type": "test_event2"},
"condition": [
{
"condition": "device",
"domain": DOMAIN,
"device_id": "",
"entity_id": ent1.entity_id,
"type": "is_off",
}
],
"action": {
"service": "test.automation",
"data_template": {
"some": "is_off {{ trigger.%s }}"
% "}} - {{ trigger.".join(("platform", "event.event_type"))
},
},
},
]
},
)
await hass.async_block_till_done()
assert hass.states.get(ent1.entity_id).state == STATE_ON
assert len(calls) == 0
hass.bus.async_fire("test_event1")
hass.bus.async_fire("test_event2")
await hass.async_block_till_done()
assert len(calls) == 1
assert calls[0].data["some"] == "is_on event - test_event1"
hass.states.async_set(ent1.entity_id, STATE_OFF)
hass.bus.async_fire("test_event1")
hass.bus.async_fire("test_event2")
await hass.async_block_till_done()
assert len(calls) == 2
assert calls[1].data["some"] == "is_off event - test_event2"
async def test_action(hass, calls):
"""Test for turn_on and turn_off actions."""
platform = getattr(hass.components, f"test.{DOMAIN}")
platform.init()
assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}})
ent1, ent2, ent3 = platform.ENTITIES
assert await async_setup_component(
hass,
automation.DOMAIN,
{
automation.DOMAIN: [
{
"trigger": {"platform": "event", "event_type": "test_event1"},
"action": {
"domain": DOMAIN,
"device_id": "",
"entity_id": ent1.entity_id,
"type": "turn_off",
},
},
{
"trigger": {"platform": "event", "event_type": "test_event2"},
"action": {
"domain": DOMAIN,
"device_id": "",
"entity_id": ent1.entity_id,
"type": "turn_on",
},
},
{
"trigger": {"platform": "event", "event_type": "test_event3"},
"action": {
"domain": DOMAIN,
"device_id": "",
"entity_id": ent1.entity_id,
"type": "toggle",
},
},
]
},
)
await hass.async_block_till_done()
assert hass.states.get(ent1.entity_id).state == STATE_ON
assert len(calls) == 0
hass.bus.async_fire("test_event1")
await hass.async_block_till_done()
assert hass.states.get(ent1.entity_id).state == STATE_OFF
hass.bus.async_fire("test_event1")
await hass.async_block_till_done()
assert hass.states.get(ent1.entity_id).state == STATE_OFF
hass.bus.async_fire("test_event2")
await hass.async_block_till_done()
assert hass.states.get(ent1.entity_id).state == STATE_ON
hass.bus.async_fire("test_event2")
await hass.async_block_till_done()
assert hass.states.get(ent1.entity_id).state == STATE_ON
hass.bus.async_fire("test_event3")
await hass.async_block_till_done()
assert hass.states.get(ent1.entity_id).state == STATE_OFF
hass.bus.async_fire("test_event3")
await hass.async_block_till_done()
assert hass.states.get(ent1.entity_id).state == STATE_ON

View file

@ -0,0 +1,138 @@
"""The test for switch device automation."""
import pytest
from homeassistant.components.switch import DOMAIN
from homeassistant.const import STATE_ON, STATE_OFF, CONF_PLATFORM
from homeassistant.setup import async_setup_component
import homeassistant.components.automation as automation
from homeassistant.components.device_automation import (
_async_get_device_automations as async_get_device_automations,
)
from homeassistant.helpers import device_registry
from tests.common import (
MockConfigEntry,
async_mock_service,
mock_device_registry,
mock_registry,
)
@pytest.fixture
def device_reg(hass):
"""Return an empty, loaded, registry."""
return mock_device_registry(hass)
@pytest.fixture
def entity_reg(hass):
"""Return an empty, loaded, registry."""
return mock_registry(hass)
@pytest.fixture
def calls(hass):
"""Track calls to a mock serivce."""
return async_mock_service(hass, "test", "automation")
async def test_get_conditions(hass, device_reg, entity_reg):
"""Test we get the expected conditions from a switch."""
config_entry = MockConfigEntry(domain="test", data={})
config_entry.add_to_hass(hass)
device_entry = device_reg.async_get_or_create(
config_entry_id=config_entry.entry_id,
connections={(device_registry.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")},
)
entity_reg.async_get_or_create(DOMAIN, "test", "5678", device_id=device_entry.id)
expected_conditions = [
{
"condition": "device",
"domain": DOMAIN,
"type": "is_off",
"device_id": device_entry.id,
"entity_id": f"{DOMAIN}.test_5678",
},
{
"condition": "device",
"domain": DOMAIN,
"type": "is_on",
"device_id": device_entry.id,
"entity_id": f"{DOMAIN}.test_5678",
},
]
conditions = await async_get_device_automations(hass, "condition", device_entry.id)
assert conditions == expected_conditions
async def test_if_state(hass, calls):
"""Test for turn_on and turn_off conditions."""
platform = getattr(hass.components, f"test.{DOMAIN}")
platform.init()
assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}})
ent1, ent2, ent3 = platform.ENTITIES
assert await async_setup_component(
hass,
automation.DOMAIN,
{
automation.DOMAIN: [
{
"trigger": {"platform": "event", "event_type": "test_event1"},
"condition": [
{
"condition": "device",
"domain": DOMAIN,
"device_id": "",
"entity_id": ent1.entity_id,
"type": "is_on",
}
],
"action": {
"service": "test.automation",
"data_template": {
"some": "is_on {{ trigger.%s }}"
% "}} - {{ trigger.".join(("platform", "event.event_type"))
},
},
},
{
"trigger": {"platform": "event", "event_type": "test_event2"},
"condition": [
{
"condition": "device",
"domain": DOMAIN,
"device_id": "",
"entity_id": ent1.entity_id,
"type": "is_off",
}
],
"action": {
"service": "test.automation",
"data_template": {
"some": "is_off {{ trigger.%s }}"
% "}} - {{ trigger.".join(("platform", "event.event_type"))
},
},
},
]
},
)
await hass.async_block_till_done()
assert hass.states.get(ent1.entity_id).state == STATE_ON
assert len(calls) == 0
hass.bus.async_fire("test_event1")
hass.bus.async_fire("test_event2")
await hass.async_block_till_done()
assert len(calls) == 1
assert calls[0].data["some"] == "is_on event - test_event1"
hass.states.async_set(ent1.entity_id, STATE_OFF)
hass.bus.async_fire("test_event1")
hass.bus.async_fire("test_event2")
await hass.async_block_till_done()
assert len(calls) == 2
assert calls[1].data["some"] == "is_off event - test_event2"

View file

@ -0,0 +1,147 @@
"""The test for switch device automation."""
import pytest
from homeassistant.components.switch import DOMAIN
from homeassistant.const import STATE_ON, STATE_OFF, CONF_PLATFORM
from homeassistant.setup import async_setup_component
import homeassistant.components.automation as automation
from homeassistant.helpers import device_registry
from tests.common import (
MockConfigEntry,
async_mock_service,
mock_device_registry,
mock_registry,
async_get_device_automations,
)
@pytest.fixture
def device_reg(hass):
"""Return an empty, loaded, registry."""
return mock_device_registry(hass)
@pytest.fixture
def entity_reg(hass):
"""Return an empty, loaded, registry."""
return mock_registry(hass)
@pytest.fixture
def calls(hass):
"""Track calls to a mock serivce."""
return async_mock_service(hass, "test", "automation")
async def test_get_triggers(hass, device_reg, entity_reg):
"""Test we get the expected triggers from a switch."""
config_entry = MockConfigEntry(domain="test", data={})
config_entry.add_to_hass(hass)
device_entry = device_reg.async_get_or_create(
config_entry_id=config_entry.entry_id,
connections={(device_registry.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")},
)
entity_reg.async_get_or_create(DOMAIN, "test", "5678", device_id=device_entry.id)
expected_triggers = [
{
"platform": "device",
"domain": DOMAIN,
"type": "turned_off",
"device_id": device_entry.id,
"entity_id": f"{DOMAIN}.test_5678",
},
{
"platform": "device",
"domain": DOMAIN,
"type": "turned_on",
"device_id": device_entry.id,
"entity_id": f"{DOMAIN}.test_5678",
},
]
triggers = await async_get_device_automations(hass, "trigger", device_entry.id)
assert triggers == expected_triggers
async def test_if_fires_on_state_change(hass, calls):
"""Test for turn_on and turn_off triggers firing."""
platform = getattr(hass.components, f"test.{DOMAIN}")
platform.init()
assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}})
ent1, ent2, ent3 = platform.ENTITIES
assert await async_setup_component(
hass,
automation.DOMAIN,
{
automation.DOMAIN: [
{
"trigger": {
"platform": "device",
"domain": DOMAIN,
"device_id": "",
"entity_id": ent1.entity_id,
"type": "turned_on",
},
"action": {
"service": "test.automation",
"data_template": {
"some": "turn_on {{ trigger.%s }}"
% "}} - {{ trigger.".join(
(
"platform",
"entity_id",
"from_state.state",
"to_state.state",
"for",
)
)
},
},
},
{
"trigger": {
"platform": "device",
"domain": DOMAIN,
"device_id": "",
"entity_id": ent1.entity_id,
"type": "turned_off",
},
"action": {
"service": "test.automation",
"data_template": {
"some": "turn_off {{ trigger.%s }}"
% "}} - {{ trigger.".join(
(
"platform",
"entity_id",
"from_state.state",
"to_state.state",
"for",
)
)
},
},
},
]
},
)
await hass.async_block_till_done()
assert hass.states.get(ent1.entity_id).state == STATE_ON
assert len(calls) == 0
hass.states.async_set(ent1.entity_id, STATE_OFF)
await hass.async_block_till_done()
assert len(calls) == 1
assert calls[0].data["some"] == "turn_off device - {} - on - off - None".format(
ent1.entity_id
)
hass.states.async_set(ent1.entity_id, STATE_ON)
await hass.async_block_till_done()
assert len(calls) == 2
assert calls[1].data["some"] == "turn_on device - {} - off - on - None".format(
ent1.entity_id
)

View file

@ -4,9 +4,6 @@ from unittest.mock import patch
import pytest import pytest
import homeassistant.components.automation as automation import homeassistant.components.automation as automation
from homeassistant.components.device_automation import (
_async_get_device_automations as async_get_device_automations,
)
from homeassistant.components.switch import DOMAIN from homeassistant.components.switch import DOMAIN
from homeassistant.components.zha.core.const import CHANNEL_ON_OFF from homeassistant.components.zha.core.const import CHANNEL_ON_OFF
from homeassistant.helpers.device_registry import async_get_registry from homeassistant.helpers.device_registry import async_get_registry
@ -14,7 +11,7 @@ from homeassistant.setup import async_setup_component
from .common import async_enable_traffic, async_init_zigpy_device from .common import async_enable_traffic, async_init_zigpy_device
from tests.common import async_mock_service from tests.common import async_mock_service, async_get_device_automations
ON = 1 ON = 1
OFF = 0 OFF = 0
@ -73,9 +70,7 @@ async def test_triggers(hass, config_entry, zha_gateway):
ha_device_registry = await async_get_registry(hass) ha_device_registry = await async_get_registry(hass)
reg_device = ha_device_registry.async_get_device({("zha", ieee_address)}, set()) reg_device = ha_device_registry.async_get_device({("zha", ieee_address)}, set())
triggers = await async_get_device_automations( triggers = await async_get_device_automations(hass, "trigger", reg_device.id)
hass, "async_get_triggers", reg_device.id
)
expected_triggers = [ expected_triggers = [
{ {
@ -136,9 +131,7 @@ async def test_no_triggers(hass, config_entry, zha_gateway):
ha_device_registry = await async_get_registry(hass) ha_device_registry = await async_get_registry(hass)
reg_device = ha_device_registry.async_get_device({("zha", ieee_address)}, set()) reg_device = ha_device_registry.async_get_device({("zha", ieee_address)}, set())
triggers = await async_get_device_automations( triggers = await async_get_device_automations(hass, "trigger", reg_device.id)
hass, "async_get_triggers", reg_device.id
)
assert triggers == [] assert triggers == []