Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions homeassistant/components/device_automation/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@

async def async_setup(hass, config):
"""Set up device automation."""
hass.components.websocket_api.async_register_command(
websocket_device_automation_list_actions
)
hass.components.websocket_api.async_register_command(
websocket_device_automation_list_conditions
)
Expand Down Expand Up @@ -93,6 +96,20 @@ async def _async_get_device_automations(hass, fname, device_id):
return automations


@websocket_api.async_response
@websocket_api.websocket_command(
{
vol.Required("type"): "device_automation/action/list",
vol.Required("device_id"): str,
}
)
async def websocket_device_automation_list_actions(hass, connection, msg):
"""Handle request for device actions."""
device_id = msg["device_id"]
actions = await _async_get_device_automations(hass, "async_get_actions", device_id)
connection.send_result(msg["id"], actions)


@websocket_api.async_response
@websocket_api.websocket_command(
{
Expand Down
1 change: 1 addition & 0 deletions homeassistant/components/device_automation/const.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Constants for device automations."""
CONF_IS_OFF = "is_off"
CONF_IS_ON = "is_on"
CONF_TOGGLE = "toggle"
CONF_TURN_OFF = "turn_off"
CONF_TURN_ON = "turn_on"
67 changes: 66 additions & 1 deletion homeassistant/components/light/device_automation.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,48 @@
from homeassistant.components.device_automation.const import (
CONF_IS_OFF,
CONF_IS_ON,
CONF_TOGGLE,
CONF_TURN_OFF,
CONF_TURN_ON,
)
from homeassistant.core import split_entity_id
from homeassistant.const import (
CONF_CONDITION,
CONF_DEVICE,
CONF_DEVICE_ID,
CONF_DOMAIN,
CONF_ENTITY_ID,
CONF_PLATFORM,
CONF_TYPE,
)
from homeassistant.helpers import condition, config_validation as cv
from homeassistant.helpers import condition, config_validation as cv, service
from homeassistant.helpers.entity_registry import async_entries_for_device
from . import DOMAIN


# mypy: allow-untyped-defs, no-check-untyped-defs

ENTITY_ACTIONS = [
{
# Turn light off
CONF_DEVICE: None,
CONF_DOMAIN: DOMAIN,
CONF_TYPE: CONF_TURN_OFF,
},
{
# Turn light on
CONF_DEVICE: None,
CONF_DOMAIN: DOMAIN,
CONF_TYPE: CONF_TURN_ON,
},
{
# Toggle light
CONF_DEVICE: None,
CONF_DOMAIN: DOMAIN,
CONF_TYPE: CONF_TOGGLE,
},
]

ENTITY_CONDITIONS = [
{
# True when light is turned off
Expand Down Expand Up @@ -54,6 +77,18 @@
},
]

ACTION_SCHEMA = vol.All(
vol.Schema(
{
vol.Required(CONF_DEVICE): None,
vol.Optional(CONF_DEVICE_ID): str,
vol.Required(CONF_DOMAIN): DOMAIN,
vol.Required(CONF_ENTITY_ID): cv.entity_id,
vol.Required(CONF_TYPE): vol.In([CONF_TOGGLE, CONF_TURN_OFF, CONF_TURN_ON]),
}
)
)

CONDITION_SCHEMA = vol.All(
vol.Schema(
{
Expand Down Expand Up @@ -83,6 +118,31 @@ def _is_domain(entity, domain):
return split_entity_id(entity.entity_id)[0] == domain


async def async_action_from_config(hass, config, variables, context):
"""Change state based on configuration."""
config = ACTION_SCHEMA(config)
action_type = config[CONF_TYPE]
if action_type == CONF_TURN_ON:
action = "turn_on"
elif action_type == CONF_TURN_OFF:
action = "turn_off"
else:
action = "toggle"
service_action = {
service.CONF_SERVICE: "light.{}".format(action),
CONF_ENTITY_ID: config[CONF_ENTITY_ID],
}

await service.async_call_from_config(
hass,
service_action,
blocking=True,
variables=variables,
# validate_config=False,
context=context,
)


def async_condition_from_config(config, config_validation):
"""Evaluate state based on configuration."""
config = CONDITION_SCHEMA(config)
Expand Down Expand Up @@ -140,6 +200,11 @@ async def _async_get_automations(hass, device_id, automation_templates):
return automations


async def async_get_actions(hass, device_id):
"""List device actions."""
return await _async_get_automations(hass, device_id, ENTITY_ACTIONS)


async def async_get_conditions(hass, device_id):
"""List device conditions."""
return await _async_get_automations(hass, device_id, ENTITY_CONDITIONS)
Expand Down
5 changes: 5 additions & 0 deletions homeassistant/components/light/strings.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
{
"device_automation": {
"action_type": {
"toggle": "Toggle {name}",
"turn_on": "Turn on {name}",
"turn_off": "Turn off {name}"
},
"condition_type": {
"is_on": "{name} is on",
"is_off": "{name} is off"
Expand Down
7 changes: 7 additions & 0 deletions homeassistant/helpers/config_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
CONF_ALIAS,
CONF_BELOW,
CONF_CONDITION,
CONF_DEVICE,
CONF_DOMAIN,
CONF_ENTITY_ID,
CONF_ENTITY_NAMESPACE,
Expand Down Expand Up @@ -861,6 +862,11 @@ def validator(value):
}
)

DEVICE_ACTION_SCHEMA = vol.Schema(
{vol.Required(CONF_DEVICE): None, vol.Required(CONF_DOMAIN): str},
extra=vol.ALLOW_EXTRA,
)

SCRIPT_SCHEMA = vol.All(
ensure_list,
[
Expand All @@ -870,6 +876,7 @@ def validator(value):
_SCRIPT_WAIT_TEMPLATE_SCHEMA,
EVENT_SCHEMA,
CONDITION_SCHEMA,
DEVICE_ACTION_SCHEMA,
)
],
)
19 changes: 18 additions & 1 deletion homeassistant/helpers/script.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import voluptuous as vol

from homeassistant.core import HomeAssistant, Context, callback, CALLBACK_TYPE
from homeassistant.const import CONF_CONDITION, CONF_TIMEOUT
from homeassistant.const import CONF_CONDITION, CONF_DEVICE, CONF_DOMAIN, CONF_TIMEOUT
from homeassistant import exceptions
from homeassistant.helpers import (
service,
Expand All @@ -22,6 +22,7 @@
async_track_template,
)
from homeassistant.helpers.typing import ConfigType
from homeassistant.loader import async_get_integration
import homeassistant.util.dt as date_util
from homeassistant.util.async_ import run_coroutine_threadsafe, run_callback_threadsafe

Expand All @@ -48,6 +49,7 @@
ACTION_CHECK_CONDITION = "condition"
ACTION_FIRE_EVENT = "event"
ACTION_CALL_SERVICE = "call_service"
ACTION_DEVICE_AUTOMATION = "device"


def _determine_action(action):
Expand All @@ -64,6 +66,9 @@ def _determine_action(action):
if CONF_EVENT in action:
return ACTION_FIRE_EVENT

if CONF_DEVICE in action:
return ACTION_DEVICE_AUTOMATION

return ACTION_CALL_SERVICE


Expand Down Expand Up @@ -117,6 +122,7 @@ def __init__(
ACTION_CHECK_CONDITION: self._async_check_condition,
ACTION_FIRE_EVENT: self._async_fire_event,
ACTION_CALL_SERVICE: self._async_call_service,
ACTION_DEVICE_AUTOMATION: self._async_device_automation,
}

@property
Expand Down Expand Up @@ -318,6 +324,17 @@ async def _async_call_service(self, action, variables, context):
context=context,
)

async def _async_device_automation(self, action, variables, context):
"""Perform the device automation specified in the action.

This method is a coroutine.
"""
self.last_action = action.get(CONF_ALIAS, "device automation")
self._log("Executing step %s" % self.last_action)
integration = await async_get_integration(self.hass, action[CONF_DOMAIN])
platform = integration.get_platform("device_automation")
await platform.async_action_from_config(self.hass, action, variables, context)

async def _async_fire_event(self, action, variables, context):
"""Fire an event."""
self.last_action = action.get(CONF_ALIAS, action[CONF_EVENT])
Expand Down
47 changes: 47 additions & 0 deletions tests/components/device_automation/test_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,53 @@ def _same_lists(a, b):
return True


async def test_websocket_get_actions(hass, hass_ws_client, device_reg, entity_reg):
"""Test we get the expected conditions from a light through websocket."""
await async_setup_component(hass, "device_automation", {})
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("light", "test", "5678", device_id=device_entry.id)
expected_actions = [
{
"device": None,
"domain": "light",
"type": "turn_off",
"device_id": device_entry.id,
"entity_id": "light.test_5678",
},
{
"device": None,
"domain": "light",
"type": "turn_on",
"device_id": device_entry.id,
"entity_id": "light.test_5678",
},
{
"device": None,
"domain": "light",
"type": "toggle",
"device_id": device_entry.id,
"entity_id": "light.test_5678",
},
]

client = await hass_ws_client(hass)
await client.send_json(
{"id": 1, "type": "device_automation/action/list", "device_id": device_entry.id}
)
msg = await client.receive_json()

assert msg["id"] == 1
assert msg["type"] == TYPE_RESULT
assert msg["success"]
actions = msg["result"]
assert _same_lists(actions, expected_actions)


async def test_websocket_get_conditions(hass, hass_ws_client, device_reg, entity_reg):
"""Test we get the expected conditions from a light through websocket."""
await async_setup_component(hass, "device_automation", {})
Expand Down
Loading