Add websocket trigger/condition commands (#39109)
This commit is contained in:
parent
d5193e64de
commit
2a1fe9d29a
10 changed files with 204 additions and 61 deletions
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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"))}
|
||||
)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue