Raise ConditionError for zone errors (#46253)

* Raise ConditionError for zone errors

* Do not test missing state

* Handle multiple entities/zones
pull/46784/head
Anders Melchiorsen 2021-02-19 13:14:47 +01:00 committed by GitHub
parent 40068c2f1b
commit e7e3e09063
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 127 additions and 18 deletions

View File

@ -48,8 +48,11 @@ async def async_attach_trigger(hass, config, action, automation_info):
return return
zone_state = hass.states.get(zone_entity_id) zone_state = hass.states.get(zone_entity_id)
from_match = condition.zone(hass, zone_state, from_state)
to_match = condition.zone(hass, zone_state, to_state) from_match = (
condition.zone(hass, zone_state, from_state) if from_state else False
)
to_match = condition.zone(hass, zone_state, to_state) if to_state else False
if ( if (
trigger_event == EVENT_ENTER trigger_event == EVENT_ENTER

View File

@ -58,7 +58,7 @@ async def async_attach_trigger(
zone_state = hass.states.get(zone_entity_id) zone_state = hass.states.get(zone_entity_id)
from_match = condition.zone(hass, zone_state, from_s) if from_s else False from_match = condition.zone(hass, zone_state, from_s) if from_s else False
to_match = condition.zone(hass, zone_state, to_s) to_match = condition.zone(hass, zone_state, to_s) if to_s else False
if ( if (
event == EVENT_ENTER event == EVENT_ENTER

View File

@ -588,23 +588,36 @@ def zone(
Async friendly. Async friendly.
""" """
if zone_ent is None:
raise ConditionError("No zone specified")
if isinstance(zone_ent, str): if isinstance(zone_ent, str):
zone_ent_id = zone_ent
zone_ent = hass.states.get(zone_ent) zone_ent = hass.states.get(zone_ent)
if zone_ent is None: if zone_ent is None:
return False raise ConditionError(f"Unknown zone {zone_ent_id}")
if isinstance(entity, str):
entity = hass.states.get(entity)
if entity is None: if entity is None:
return False raise ConditionError("No entity specified")
if isinstance(entity, str):
entity_id = entity
entity = hass.states.get(entity)
if entity is None:
raise ConditionError(f"Unknown entity {entity_id}")
else:
entity_id = entity.entity_id
latitude = entity.attributes.get(ATTR_LATITUDE) latitude = entity.attributes.get(ATTR_LATITUDE)
longitude = entity.attributes.get(ATTR_LONGITUDE) longitude = entity.attributes.get(ATTR_LONGITUDE)
if latitude is None or longitude is None: if latitude is None:
return False raise ConditionError(f"Entity {entity_id} has no 'latitude' attribute")
if longitude is None:
raise ConditionError(f"Entity {entity_id} has no 'longitude' attribute")
return zone_cmp.in_zone( return zone_cmp.in_zone(
zone_ent, latitude, longitude, entity.attributes.get(ATTR_GPS_ACCURACY, 0) zone_ent, latitude, longitude, entity.attributes.get(ATTR_GPS_ACCURACY, 0)
@ -622,13 +635,26 @@ def zone_from_config(
def if_in_zone(hass: HomeAssistant, variables: TemplateVarsType = None) -> bool: def if_in_zone(hass: HomeAssistant, variables: TemplateVarsType = None) -> bool:
"""Test if condition.""" """Test if condition."""
return all( errors = []
any(
zone(hass, zone_entity_id, entity_id) all_ok = True
for zone_entity_id in zone_entity_ids for entity_id in entity_ids:
) entity_ok = False
for entity_id in entity_ids for zone_entity_id in zone_entity_ids:
) try:
if zone(hass, zone_entity_id, entity_id):
entity_ok = True
except ConditionError as ex:
errors.append(str(ex))
if not entity_ok:
all_ok = False
# Raise the errors only if no definitive result was found
if errors and not all_ok:
raise ConditionError("Error in 'zone' condition: " + ", ".join(errors))
return all_ok
return if_in_zone return if_in_zone

View File

@ -811,6 +811,86 @@ async def test_numeric_state_using_input_number(hass):
) )
async def test_zone_raises(hass):
"""Test that zone raises ConditionError on errors."""
test = await condition.async_from_config(
hass,
{
"condition": "zone",
"entity_id": "device_tracker.cat",
"zone": "zone.home",
},
)
with pytest.raises(ConditionError, match="Unknown zone"):
test(hass)
hass.states.async_set(
"zone.home",
"zoning",
{"name": "home", "latitude": 2.1, "longitude": 1.1, "radius": 10},
)
with pytest.raises(ConditionError, match="Unknown entity"):
test(hass)
hass.states.async_set(
"device_tracker.cat",
"home",
{"friendly_name": "cat"},
)
with pytest.raises(ConditionError, match="latitude"):
test(hass)
hass.states.async_set(
"device_tracker.cat",
"home",
{"friendly_name": "cat", "latitude": 2.1},
)
with pytest.raises(ConditionError, match="longitude"):
test(hass)
hass.states.async_set(
"device_tracker.cat",
"home",
{"friendly_name": "cat", "latitude": 2.1, "longitude": 1.1},
)
# All okay, now test multiple failed conditions
assert test(hass)
test = await condition.async_from_config(
hass,
{
"condition": "zone",
"entity_id": ["device_tracker.cat", "device_tracker.dog"],
"zone": ["zone.home", "zone.work"],
},
)
with pytest.raises(ConditionError, match="dog"):
test(hass)
with pytest.raises(ConditionError, match="work"):
test(hass)
hass.states.async_set(
"zone.work",
"zoning",
{"name": "work", "latitude": 20, "longitude": 10, "radius": 25000},
)
hass.states.async_set(
"device_tracker.dog",
"work",
{"friendly_name": "dog", "latitude": 20.1, "longitude": 10.1},
)
assert test(hass)
async def test_zone_multiple_entities(hass): async def test_zone_multiple_entities(hass):
"""Test with multiple entities in condition.""" """Test with multiple entities in condition."""
test = await condition.async_from_config( test = await condition.async_from_config(