Add websocket trigger/condition commands (#39109)

This commit is contained in:
Paulus Schoutsen 2020-08-24 23:01:57 +02:00 committed by GitHub
parent d5193e64de
commit 2a1fe9d29a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 204 additions and 61 deletions

View file

@ -377,7 +377,7 @@ class AutomationEntity(ToggleEntity, RestoreEntity):
else:
await self.async_disable()
async def async_trigger(self, variables, skip_condition=False, context=None):
async def async_trigger(self, variables, context=None, skip_condition=False):
"""Trigger automation.
This method is a coroutine.

View file

@ -47,10 +47,9 @@ async def async_attach_trigger(
return
hass.async_run_job(
action(
{"trigger": {"platform": platform_type, "event": event}},
context=event.context,
)
action,
{"trigger": {"platform": platform_type, "event": event}},
event.context,
)
return hass.bus.async_listen(event_type, handle_event)

View file

@ -30,10 +30,9 @@ async def async_attach_trigger(hass, config, action, automation_info):
def hass_shutdown(event):
"""Execute when Home Assistant is shutting down."""
hass.async_run_job(
action(
{"trigger": {"platform": "homeassistant", "event": event}},
context=event.context,
)
action,
{"trigger": {"platform": "homeassistant", "event": event}},
event.context,
)
return hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, hass_shutdown)

View file

@ -103,20 +103,19 @@ async def async_attach_trigger(
def call_action():
"""Call action with right context."""
hass.async_run_job(
action(
{
"trigger": {
"platform": platform_type,
"entity_id": entity,
"below": below,
"above": above,
"from_state": from_s,
"to_state": to_s,
"for": time_delta if not time_delta else period[entity],
}
},
context=to_s.context,
)
action,
{
"trigger": {
"platform": platform_type,
"entity_id": entity,
"below": below,
"above": above,
"from_state": from_s,
"to_state": to_s,
"for": time_delta if not time_delta else period[entity],
}
},
to_s.context,
)
matching = check_numeric_state(entity, from_s, to_s)

View file

@ -83,18 +83,17 @@ async def async_attach_trigger(
def call_action():
"""Call action with right context."""
hass.async_run_job(
action(
{
"trigger": {
"platform": platform_type,
"entity_id": entity,
"from_state": from_s,
"to_state": to_s,
"for": time_delta if not time_delta else period[entity],
}
},
context=event.context,
)
action,
{
"trigger": {
"platform": platform_type,
"entity_id": entity,
"from_state": from_s,
"to_state": to_s,
"for": time_delta if not time_delta else period[entity],
}
},
event.context,
)
if not time_delta:

View file

@ -54,18 +54,17 @@ async def async_attach_trigger(
def call_action(*_):
"""Call action with right context."""
hass.async_run_job(
action(
{
"trigger": {
"platform": "template",
"entity_id": entity_id,
"from_state": from_s,
"to_state": to_s,
"for": time_delta if not time_delta else period,
}
},
context=(to_s.context if to_s else None),
)
action,
{
"trigger": {
"platform": "template",
"entity_id": entity_id,
"from_state": from_s,
"to_state": to_s,
"for": time_delta if not time_delta else period,
}
},
(to_s.context if to_s else None),
)
if not time_delta:

View file

@ -40,6 +40,8 @@ def async_register_commands(hass, async_reg):
async_reg(hass, handle_manifest_list)
async_reg(hass, handle_manifest_get)
async_reg(hass, handle_entity_source)
async_reg(hass, handle_subscribe_trigger)
async_reg(hass, handle_test_condition)
def pong_message(iden):
@ -315,3 +317,69 @@ def handle_entity_source(hass, connection, msg):
sources[entity_id] = source
connection.send_result(msg["id"], sources)
@callback
@decorators.websocket_command(
{
vol.Required("type"): "subscribe_trigger",
vol.Required("trigger"): cv.TRIGGER_SCHEMA,
vol.Optional("variables"): dict,
}
)
@decorators.require_admin
@decorators.async_response
async def handle_subscribe_trigger(hass, connection, msg):
"""Handle subscribe trigger command."""
# Circular dep
# pylint: disable=import-outside-toplevel
from homeassistant.helpers import trigger
trigger_config = await trigger.async_validate_trigger_config(hass, msg["trigger"])
@callback
def forward_triggers(variables, context=None):
"""Forward events to websocket."""
connection.send_message(
messages.event_message(
msg["id"], {"variables": variables, "context": context}
)
)
connection.subscriptions[msg["id"]] = (
await trigger.async_initialize_triggers(
hass,
trigger_config,
forward_triggers,
const.DOMAIN,
const.DOMAIN,
connection.logger.log,
variables=msg.get("variables"),
)
) or (
# Some triggers won't return an unsub function. Since the caller expects
# a subscription, we're going to fake one.
lambda: None
)
connection.send_result(msg["id"])
@decorators.websocket_command(
{
vol.Required("type"): "test_condition",
vol.Required("condition"): cv.CONDITION_SCHEMA,
vol.Optional("variables"): dict,
}
)
@decorators.require_admin
@decorators.async_response
async def handle_test_condition(hass, connection, msg):
"""Handle test condition command."""
# Circular dep
# pylint: disable=import-outside-toplevel
from homeassistant.helpers import condition
check_condition = await condition.async_from_config(hass, msg["condition"])
connection.send_result(
msg["id"], {"result": check_condition(hass, msg.get("variables"))}
)

View file

@ -57,19 +57,18 @@ async def async_attach_trigger(hass, config, action, automation_info):
and not to_match
):
hass.async_run_job(
action(
{
"trigger": {
"platform": "zone",
"entity_id": entity,
"from_state": from_s,
"to_state": to_s,
"zone": zone_state,
"event": event,
}
},
context=to_s.context,
)
action,
{
"trigger": {
"platform": "zone",
"entity_id": entity,
"from_state": from_s,
"to_state": to_s,
"zone": zone_state,
"event": event,
}
},
to_s.context,
)
return async_track_state_change_event(hass, entity_id, zone_automation_listener)

View file

@ -302,6 +302,9 @@ class HomeAssistant:
target: target to call.
args: parameters for method to call.
"""
if target is None:
raise ValueError("Don't call async_add_job with None")
task = None
# Check for partials to properly determine if coroutine function

View file

@ -8,7 +8,7 @@ from homeassistant.components.websocket_api.auth import (
TYPE_AUTH_REQUIRED,
)
from homeassistant.components.websocket_api.const import URL
from homeassistant.core import callback
from homeassistant.core import Context, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import entity
from homeassistant.loader import async_get_integration
@ -654,3 +654,81 @@ async def test_entity_source_admin(hass, websocket_client, hass_admin_user):
assert msg["type"] == const.TYPE_RESULT
assert not msg["success"]
assert msg["error"]["code"] == const.ERR_UNAUTHORIZED
async def test_subscribe_trigger(hass, websocket_client):
"""Test subscribing to a trigger."""
init_count = sum(hass.bus.async_listeners().values())
await websocket_client.send_json(
{
"id": 5,
"type": "subscribe_trigger",
"trigger": {"platform": "event", "event_type": "test_event"},
"variables": {"hello": "world"},
}
)
msg = await websocket_client.receive_json()
assert msg["id"] == 5
assert msg["type"] == const.TYPE_RESULT
assert msg["success"]
# Verify we have a new listener
assert sum(hass.bus.async_listeners().values()) == init_count + 1
context = Context()
hass.bus.async_fire("ignore_event")
hass.bus.async_fire("test_event", {"hello": "world"}, context=context)
hass.bus.async_fire("ignore_event")
with timeout(3):
msg = await websocket_client.receive_json()
assert msg["id"] == 5
assert msg["type"] == "event"
assert msg["event"]["context"]["id"] == context.id
assert msg["event"]["variables"]["trigger"]["platform"] == "event"
event = msg["event"]["variables"]["trigger"]["event"]
assert event["event_type"] == "test_event"
assert event["data"] == {"hello": "world"}
assert event["origin"] == "LOCAL"
await websocket_client.send_json(
{"id": 6, "type": "unsubscribe_events", "subscription": 5}
)
msg = await websocket_client.receive_json()
assert msg["id"] == 6
assert msg["type"] == const.TYPE_RESULT
assert msg["success"]
# Check our listener got unsubscribed
assert sum(hass.bus.async_listeners().values()) == init_count
async def test_test_condition(hass, websocket_client):
"""Test testing a condition."""
hass.states.async_set("hello.world", "paulus")
await websocket_client.send_json(
{
"id": 5,
"type": "test_condition",
"condition": {
"condition": "state",
"entity_id": "hello.world",
"state": "paulus",
},
"variables": {"hello": "world"},
}
)
msg = await websocket_client.receive_json()
assert msg["id"] == 5
assert msg["type"] == const.TYPE_RESULT
assert msg["success"]
assert msg["result"]["result"] is True