Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for `for` to binary_sensor, light and switch device conditions #27153

Merged
Merged
Changes from all commits
Commits
File filter...
Filter file types
Jump to…
Jump to file or symbol
Failed to load files and symbols.

Always

Just for now

@@ -4,7 +4,7 @@

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.const import ATTR_DEVICE_CLASS, CONF_ENTITY_ID, CONF_FOR, CONF_TYPE
from homeassistant.helpers import condition, config_validation as cv
from homeassistant.helpers.entity_registry import (
async_entries_for_device,
@@ -188,6 +188,7 @@
{
vol.Required(CONF_ENTITY_ID): cv.entity_id,
vol.Required(CONF_TYPE): vol.In(IS_OFF + IS_ON),
vol.Optional(CONF_FOR): cv.positive_time_period_dict,
}
)

@@ -244,5 +245,16 @@ def async_condition_from_config(
condition.CONF_ENTITY_ID: config[CONF_ENTITY_ID],
condition.CONF_STATE: stat,
}
if CONF_FOR in config:
state_config[CONF_FOR] = config[CONF_FOR]

return condition.state_from_config(state_config, config_validation)


async def async_get_condition_capabilities(hass: HomeAssistant, config: dict) -> dict:
"""List condition capabilities."""
return {
"extra_fields": vol.Schema(
{vol.Optional(CONF_FOR): cv.positive_time_period_dict}
)
}
@@ -240,7 +240,7 @@
return triggers


async def async_get_trigger_capabilities(hass, trigger):
async def async_get_trigger_capabilities(hass, config):
"""List trigger capabilities."""
return {
"extra_fields": vol.Schema(
@@ -59,6 +59,9 @@
hass.components.websocket_api.async_register_command(
websocket_device_automation_list_triggers
)
hass.components.websocket_api.async_register_command(
websocket_device_automation_get_condition_capabilities
)
hass.components.websocket_api.async_register_command(
websocket_device_automation_get_trigger_capabilities
)
@@ -206,6 +209,22 @@
connection.send_result(msg["id"], triggers)


@websocket_api.async_response
@websocket_api.websocket_command(
{
vol.Required("type"): "device_automation/condition/capabilities",
vol.Required("condition"): dict,
}
)
async def websocket_device_automation_get_condition_capabilities(hass, connection, msg):
"""Handle request for device condition capabilities."""
condition = msg["condition"]
capabilities = await _async_get_device_automation_capabilities(
hass, "condition", condition
)
connection.send_result(msg["id"], capabilities)


@websocket_api.async_response
@websocket_api.websocket_command(
{
@@ -80,6 +80,7 @@
{
vol.Required(CONF_ENTITY_ID): cv.entity_id,
vol.Required(CONF_TYPE): vol.In([CONF_IS_OFF, CONF_IS_ON]),
vol.Optional(CONF_FOR): cv.positive_time_period_dict,
}
)

@@ -132,6 +133,8 @@ def async_condition_from_config(
condition.CONF_ENTITY_ID: config[CONF_ENTITY_ID],
condition.CONF_STATE: stat,
}
if CONF_FOR in config:
state_config[CONF_FOR] = config[CONF_FOR]

return condition.state_from_config(state_config, config_validation)

@@ -213,7 +216,16 @@ def async_condition_from_config(
return await _async_get_automations(hass, device_id, ENTITY_TRIGGERS, domain)


async def async_get_trigger_capabilities(hass: HomeAssistant, trigger: dict) -> dict:
async def async_get_condition_capabilities(hass: HomeAssistant, config: dict) -> dict:
"""List condition capabilities."""
return {
"extra_fields": vol.Schema(
{vol.Optional(CONF_FOR): cv.positive_time_period_dict}
)
}


async def async_get_trigger_capabilities(hass: HomeAssistant, config: dict) -> dict:
"""List trigger capabilities."""
return {
"extra_fields": vol.Schema(
@@ -27,3 +27,8 @@ def async_condition_from_config(
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)


async def async_get_condition_capabilities(hass: HomeAssistant, config: dict) -> dict:
"""List condition capabilities."""
return await toggle_entity.async_get_condition_capabilities(hass, config)
@@ -32,6 +32,6 @@
return await toggle_entity.async_get_triggers(hass, device_id, DOMAIN)


async def async_get_trigger_capabilities(hass: HomeAssistant, trigger: dict) -> dict:
async def async_get_trigger_capabilities(hass: HomeAssistant, config: dict) -> dict:
"""List trigger capabilities."""
return await toggle_entity.async_get_trigger_capabilities(hass, trigger)
return await toggle_entity.async_get_trigger_capabilities(hass, config)
@@ -136,7 +136,7 @@
return triggers


async def async_get_trigger_capabilities(hass, trigger):
async def async_get_trigger_capabilities(hass, config):
"""List trigger capabilities."""
return {
"extra_fields": vol.Schema(
@@ -27,3 +27,8 @@ def async_condition_from_config(
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)


async def async_get_condition_capabilities(hass: HomeAssistant, config: dict) -> dict:
"""List condition capabilities."""
return await toggle_entity.async_get_condition_capabilities(hass, config)
@@ -32,6 +32,6 @@
return await toggle_entity.async_get_triggers(hass, device_id, DOMAIN)


async def async_get_trigger_capabilities(hass: HomeAssistant, trigger: dict) -> dict:
async def async_get_trigger_capabilities(hass: HomeAssistant, config: dict) -> dict:
"""List trigger capabilities."""
return await toggle_entity.async_get_trigger_capabilities(hass, trigger)
return await toggle_entity.async_get_trigger_capabilities(hass, config)
@@ -1,19 +1,23 @@
"""The test for binary_sensor device automation."""
from datetime import timedelta
import pytest
from unittest.mock import patch

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
import homeassistant.util.dt as dt_util

from tests.common import (
MockConfigEntry,
async_mock_service,
mock_device_registry,
mock_registry,
async_get_device_automations,
async_get_device_automation_capabilities,
)


@@ -71,6 +75,28 @@ def calls(hass):
assert conditions == expected_conditions


async def test_get_condition_capabilities(hass, device_reg, entity_reg):
"""Test we get the expected capabilities from a binary_sensor condition."""
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_capabilities = {
"extra_fields": [
{"name": "for", "optional": True, "type": "positive_time_period_dict"}
]
}
conditions = await async_get_device_automations(hass, "condition", device_entry.id)
for condition in conditions:
capabilities = await async_get_device_automation_capabilities(
hass, "condition", condition
)
assert capabilities == expected_capabilities


async def test_if_state(hass, calls):
"""Test for turn_on and turn_off conditions."""
platform = getattr(hass.components, f"test.{DOMAIN}")
@@ -131,7 +157,6 @@ def calls(hass):
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"
@@ -142,3 +167,73 @@ def calls(hass):
await hass.async_block_till_done()
assert len(calls) == 2
assert calls[1].data["some"] == "is_off event - test_event2"


async def test_if_fires_on_for_condition(hass, calls):
"""Test for firing if condition is on with delay."""
point1 = dt_util.utcnow()
point2 = point1 + timedelta(seconds=10)
point3 = point2 + timedelta(seconds=10)

platform = getattr(hass.components, f"test.{DOMAIN}")

platform.init()
assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}})

sensor1 = platform.ENTITIES["battery"]

with patch("homeassistant.core.dt_util.utcnow") as mock_utcnow:
mock_utcnow.return_value = point1
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_not_bat_low",
"for": {"seconds": 5},
},
"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")
await hass.async_block_till_done()
assert len(calls) == 0

# Time travel 10 secs into the future
mock_utcnow.return_value = point2
hass.bus.async_fire("test_event1")
await hass.async_block_till_done()
assert len(calls) == 0

hass.states.async_set(sensor1.entity_id, STATE_OFF)
hass.bus.async_fire("test_event1")
await hass.async_block_till_done()
assert len(calls) == 0

# Time travel 20 secs into the future
mock_utcnow.return_value = point3
hass.bus.async_fire("test_event1")
await hass.async_block_till_done()
assert len(calls) == 1
assert calls[0].data["some"] == "is_off event - test_event1"
@@ -170,6 +170,103 @@ def _same_lists(a, b):
assert _same_lists(triggers, expected_triggers)


async def test_websocket_get_condition_capabilities(
hass, hass_ws_client, device_reg, entity_reg
):
"""Test we get the expected condition capabilities for 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_capabilities = {
"extra_fields": [
{"name": "for", "optional": True, "type": "positive_time_period_dict"}
]
}

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

assert msg["id"] == 1
assert msg["type"] == TYPE_RESULT
assert msg["success"]
conditions = msg["result"]

id = 2
for condition in conditions:
await client.send_json(
{
"id": id,
"type": "device_automation/condition/capabilities",
"condition": condition,
}
)
msg = await client.receive_json()
assert msg["id"] == id
assert msg["type"] == TYPE_RESULT
assert msg["success"]
capabilities = msg["result"]
assert capabilities == expected_capabilities
id = id + 1


async def test_websocket_get_bad_condition_capabilities(
hass, hass_ws_client, device_reg, entity_reg
):
"""Test we get no condition capabilities for a non existing domain."""
await async_setup_component(hass, "device_automation", {})
expected_capabilities = {}

client = await hass_ws_client(hass)
await client.send_json(
{
"id": 1,
"type": "device_automation/condition/capabilities",
"condition": {"domain": "beer"},
}
)
msg = await client.receive_json()
assert msg["id"] == 1
assert msg["type"] == TYPE_RESULT
assert msg["success"]
capabilities = msg["result"]
assert capabilities == expected_capabilities


async def test_websocket_get_no_condition_capabilities(
hass, hass_ws_client, device_reg, entity_reg
):
"""Test we get no condition capabilities for a domain with no device condition capabilities."""
await async_setup_component(hass, "device_automation", {})
expected_capabilities = {}

client = await hass_ws_client(hass)
await client.send_json(
{
"id": 1,
"type": "device_automation/condition/capabilities",
"condition": {"domain": "deconz"},
}
)
msg = await client.receive_json()
assert msg["id"] == 1
assert msg["type"] == TYPE_RESULT
assert msg["success"]
capabilities = msg["result"]
assert capabilities == expected_capabilities


async def test_websocket_get_trigger_capabilities(
hass, hass_ws_client, device_reg, entity_reg
):
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.