Documentation Index
Fetch the complete documentation index at: https://mintlify.com/home-assistant/core/llms.txt
Use this file to discover all available pages before exploring further.
Device automation allows users to create automations based on device events, states, and actions without needing to know entity IDs. This guide covers how to implement device triggers, conditions, and actions for your integration.
Overview
Device automation consists of three components:
- Triggers: Events that start an automation (e.g., “button pressed”)
- Conditions: States that must be true for automation to run (e.g., “light is on”)
- Actions: Things the automation can do (e.g., “turn on light”)
Benefits of Device Automation
- User-friendly: Users select devices instead of entity IDs
- Discovery: Automations are suggested based on available devices
- Type-safe: Structured configuration prevents errors
- UI-first: Fully integrated with the automation UI
File Structure
Device automation implementations use separate files:
homeassistant/components/your_integration/
├── device_trigger.py # Trigger implementation
├── device_condition.py # Condition implementation
└── device_action.py # Action implementation
Implementing Device Triggers
Device triggers fire when specific events occur on a device.
Basic Trigger Implementation
"""Provides device triggers for Your Integration."""
from __future__ import annotations
from typing import Any
import voluptuous as vol
from homeassistant.components.device_automation import DEVICE_TRIGGER_BASE_SCHEMA
from homeassistant.components.homeassistant.triggers import event as event_trigger
from homeassistant.const import (
CONF_DEVICE_ID,
CONF_DOMAIN,
CONF_PLATFORM,
CONF_TYPE,
)
from homeassistant.core import CALLBACK_TYPE, HomeAssistant
from homeassistant.helpers import config_validation as cv, device_registry as dr
from homeassistant.helpers.trigger import TriggerActionType, TriggerInfo
from homeassistant.helpers.typing import ConfigType
from .const import DOMAIN
# Define trigger types
TRIGGER_TYPE_BUTTON_PRESSED = "button_pressed"
TRIGGER_TYPE_BUTTON_RELEASED = "button_released"
TRIGGER_TYPES = {
TRIGGER_TYPE_BUTTON_PRESSED,
TRIGGER_TYPE_BUTTON_RELEASED,
}
# Schema for trigger configuration
TRIGGER_SCHEMA = DEVICE_TRIGGER_BASE_SCHEMA.extend(
{
vol.Required(CONF_TYPE): vol.In(TRIGGER_TYPES),
}
)
async def async_get_triggers(
hass: HomeAssistant, device_id: str
) -> list[dict[str, Any]]:
"""List device triggers for a device."""
device_registry = dr.async_get(hass)
device = device_registry.async_get(device_id)
if not device:
return []
# Return available triggers for this device
triggers = []
# Example: Button device supports both press and release
if is_button_device(device):
triggers.extend(
[
{
CONF_PLATFORM: "device",
CONF_DOMAIN: DOMAIN,
CONF_DEVICE_ID: device_id,
CONF_TYPE: TRIGGER_TYPE_BUTTON_PRESSED,
},
{
CONF_PLATFORM: "device",
CONF_DOMAIN: DOMAIN,
CONF_DEVICE_ID: device_id,
CONF_TYPE: TRIGGER_TYPE_BUTTON_RELEASED,
},
]
)
return triggers
async def async_attach_trigger(
hass: HomeAssistant,
config: ConfigType,
action: TriggerActionType,
trigger_info: TriggerInfo,
) -> CALLBACK_TYPE:
"""Attach a trigger."""
# Map the device trigger to an event trigger
event_config = event_trigger.TRIGGER_SCHEMA(
{
event_trigger.CONF_PLATFORM: "event",
event_trigger.CONF_EVENT_TYPE: f"{DOMAIN}_event",
event_trigger.CONF_EVENT_DATA: {
CONF_DEVICE_ID: config[CONF_DEVICE_ID],
CONF_TYPE: config[CONF_TYPE],
},
}
)
return await event_trigger.async_attach_trigger(
hass, event_config, action, trigger_info, platform_type="device"
)
async def async_get_trigger_capabilities(
hass: HomeAssistant, config: ConfigType
) -> dict[str, vol.Schema]:
"""List trigger capabilities."""
# Return additional fields that can be configured
return {
"extra_fields": vol.Schema(
{
# Add optional fields like button number, etc.
}
)
}
def is_button_device(device: dr.DeviceEntry) -> bool:
"""Check if device is a button."""
# Implement logic to determine device type
return True
Firing Device Triggers
In your integration code, fire events when triggers occur:
# When a button is pressed:
hass.bus.async_fire(
f"{DOMAIN}_event",
{
CONF_DEVICE_ID: device_id,
CONF_TYPE: "button_pressed",
},
)
Trigger Types Examples
Common trigger types:
# Button triggers
TRIGGER_TYPE_BUTTON_SHORT_PRESS = "button_short_press"
TRIGGER_TYPE_BUTTON_LONG_PRESS = "button_long_press"
TRIGGER_TYPE_BUTTON_DOUBLE_PRESS = "button_double_press"
# Motion sensor triggers
TRIGGER_TYPE_MOTION_DETECTED = "motion_detected"
TRIGGER_TYPE_MOTION_CLEARED = "motion_cleared"
# Door/window sensor triggers
TRIGGER_TYPE_OPENED = "opened"
TRIGGER_TYPE_CLOSED = "closed"
Implementing Device Conditions
Device conditions check device state in automations.
Basic Condition Implementation
"""Provides device conditions for Your Integration."""
from __future__ import annotations
import voluptuous as vol
from homeassistant.const import (
CONF_CONDITION,
CONF_DEVICE_ID,
CONF_DOMAIN,
CONF_ENTITY_ID,
CONF_TYPE,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import (
condition,
config_validation as cv,
device_registry as dr,
entity_registry as er,
)
from homeassistant.helpers.typing import ConfigType
from .const import DOMAIN
# Define condition types
CONDITION_TYPE_IS_ON = "is_on"
CONDITION_TYPE_IS_OFF = "is_off"
CONDITION_TYPES = {
CONDITION_TYPE_IS_ON,
CONDITION_TYPE_IS_OFF,
}
# Schema for condition configuration
CONDITION_SCHEMA = cv.DEVICE_CONDITION_BASE_SCHEMA.extend(
{
vol.Required(CONF_ENTITY_ID): cv.entity_id,
vol.Required(CONF_TYPE): vol.In(CONDITION_TYPES),
}
)
async def async_get_conditions(
hass: HomeAssistant, device_id: str
) -> list[dict[str, Any]]:
"""List device conditions for a device."""
conditions = []
device_registry = dr.async_get(hass)
entity_registry = er.async_get(hass)
# Get all entities for this device
entries = er.async_entries_for_device(entity_registry, device_id)
for entry in entries:
# Only include switch entities
if entry.domain != "switch":
continue
conditions.extend(
[
{
CONF_CONDITION: "device",
CONF_DEVICE_ID: device_id,
CONF_DOMAIN: DOMAIN,
CONF_ENTITY_ID: entry.entity_id,
CONF_TYPE: CONDITION_TYPE_IS_ON,
},
{
CONF_CONDITION: "device",
CONF_DEVICE_ID: device_id,
CONF_DOMAIN: DOMAIN,
CONF_ENTITY_ID: entry.entity_id,
CONF_TYPE: CONDITION_TYPE_IS_OFF,
},
]
)
return conditions
@callback
def async_condition_from_config(
hass: HomeAssistant, config: ConfigType
) -> condition.ConditionCheckerType:
"""Create a condition checker from config."""
condition_type = config[CONF_TYPE]
entity_id = config[CONF_ENTITY_ID]
if condition_type == CONDITION_TYPE_IS_ON:
state = "on"
else:
state = "off"
@callback
def test_is_state(hass: HomeAssistant, variables: dict = None) -> bool:
"""Test if condition is true."""
return condition.state(hass, entity_id, state)
return test_is_state
async def async_get_condition_capabilities(
hass: HomeAssistant, config: ConfigType
) -> dict[str, vol.Schema]:
"""List condition capabilities."""
return {}
Implementing Device Actions
Device actions perform operations in automations.
Basic Action Implementation
"""Provides device actions for Your Integration."""
from __future__ import annotations
import voluptuous as vol
from homeassistant.const import (
CONF_DEVICE_ID,
CONF_DOMAIN,
CONF_ENTITY_ID,
CONF_TYPE,
)
from homeassistant.core import Context, HomeAssistant
from homeassistant.helpers import config_validation as cv, entity_registry as er
from homeassistant.helpers.typing import ConfigType
from .const import DOMAIN
# Define action types
ACTION_TYPE_TURN_ON = "turn_on"
ACTION_TYPE_TURN_OFF = "turn_off"
ACTION_TYPE_TOGGLE = "toggle"
ACTION_TYPES = {
ACTION_TYPE_TURN_ON,
ACTION_TYPE_TURN_OFF,
ACTION_TYPE_TOGGLE,
}
# Schema for action configuration
ACTION_SCHEMA = cv.DEVICE_ACTION_BASE_SCHEMA.extend(
{
vol.Required(CONF_TYPE): vol.In(ACTION_TYPES),
vol.Required(CONF_ENTITY_ID): cv.entity_domain("switch"),
}
)
async def async_get_actions(
hass: HomeAssistant, device_id: str
) -> list[dict[str, str]]:
"""List device actions for a device."""
actions = []
entity_registry = er.async_get(hass)
# Get all entities for this device
entries = er.async_entries_for_device(entity_registry, device_id)
for entry in entries:
# Only include switch entities
if entry.domain != "switch":
continue
actions.extend(
[
{
CONF_DEVICE_ID: device_id,
CONF_DOMAIN: DOMAIN,
CONF_ENTITY_ID: entry.entity_id,
CONF_TYPE: ACTION_TYPE_TURN_ON,
},
{
CONF_DEVICE_ID: device_id,
CONF_DOMAIN: DOMAIN,
CONF_ENTITY_ID: entry.entity_id,
CONF_TYPE: ACTION_TYPE_TURN_OFF,
},
{
CONF_DEVICE_ID: device_id,
CONF_DOMAIN: DOMAIN,
CONF_ENTITY_ID: entry.entity_id,
CONF_TYPE: ACTION_TYPE_TOGGLE,
},
]
)
return actions
async def async_call_action_from_config(
hass: HomeAssistant,
config: ConfigType,
variables: dict,
context: Context | None,
) -> None:
"""Execute a device action."""
action_type = config[CONF_TYPE]
entity_id = config[CONF_ENTITY_ID]
service_data = {CONF_ENTITY_ID: entity_id}
if action_type == ACTION_TYPE_TURN_ON:
service = "turn_on"
elif action_type == ACTION_TYPE_TURN_OFF:
service = "turn_off"
else: # ACTION_TYPE_TOGGLE
service = "toggle"
await hass.services.async_call(
"switch",
service,
service_data,
blocking=True,
context=context,
)
async def async_get_action_capabilities(
hass: HomeAssistant, config: ConfigType
) -> dict[str, vol.Schema]:
"""List action capabilities."""
# Return additional fields that can be configured
return {
"extra_fields": vol.Schema(
{
# Add optional fields like brightness, color, etc.
}
)
}
Advanced: Entity-Based Device Automation
For simple entity-based device automation, you can use the built-in helpers:
"""Simplified entity-based device triggers."""
from homeassistant.components.device_automation import toggle_entity
# For binary sensors with simple on/off triggers
async def async_get_triggers(hass, device_id):
"""List device triggers."""
return await toggle_entity.async_get_triggers(hass, device_id, "binary_sensor")
async def async_attach_trigger(hass, config, action, trigger_info):
"""Attach a trigger."""
return await toggle_entity.async_attach_trigger(
hass, config, action, trigger_info
)
Translations
Provide translations for device automation UI:
{
"device_automation": {
"trigger_type": {
"button_pressed": "Button pressed",
"button_released": "Button released"
},
"condition_type": {
"is_on": "{entity_name} is on",
"is_off": "{entity_name} is off"
},
"action_type": {
"turn_on": "Turn on {entity_name}",
"turn_off": "Turn off {entity_name}",
"toggle": "Toggle {entity_name}"
}
}
}
Testing Device Automation
Test your device automation implementation:
"""Test device triggers."""
import pytest
from homeassistant.components.device_automation import DeviceAutomationType
from homeassistant.setup import async_setup_component
async def test_get_triggers(hass, device_registry, entity_registry):
"""Test we get the expected triggers."""
# Create a test device
config_entry = MockConfigEntry(domain="your_integration")
device_entry = device_registry.async_get_or_create(
config_entry_id=config_entry.entry_id,
identifiers={("your_integration", "test-device")},
)
# Get triggers
triggers = await async_get_device_automations(
hass, DeviceAutomationType.TRIGGER, device_entry.id
)
# Verify triggers
assert len(triggers) == 2
assert triggers[0]["type"] == "button_pressed"
Best Practices
Use Clear Type Names
Choose descriptive trigger/condition/action types:
button_short_press instead of btn_sp
motion_detected instead of motion
Provide Good Translations
Translations should be clear and use entity names when available:
"is_on": "{entity_name} is on"
Handle Entity Registry
Always check if entities exist and handle missing entities gracefully.
If a device supports multiple similar actions, group them logically.
Test Thoroughly
Write tests for all trigger types, conditions, and actions.
Common Patterns
For devices with multiple buttons:
CONF_SUBTYPE = "subtype"
TRIGGER_SCHEMA = DEVICE_TRIGGER_BASE_SCHEMA.extend(
{
vol.Required(CONF_TYPE): vol.In(TRIGGER_TYPES),
vol.Required(CONF_SUBTYPE): vol.In(["button_1", "button_2", "button_3"]),
}
)
Brightness/Value Actions
For actions that accept values:
from homeassistant.components.light import ATTR_BRIGHTNESS
ACTION_SCHEMA = cv.DEVICE_ACTION_BASE_SCHEMA.extend(
{
vol.Required(CONF_TYPE): "set_brightness",
vol.Required(ATTR_BRIGHTNESS): vol.All(
vol.Coerce(int), vol.Range(min=0, max=255)
),
}
)
Next Steps