mirror of
https://github.com/home-assistant/core.git
synced 2025-07-21 12:17:07 +00:00
Add single target constraint to async_match_targets (#136643)
Add single target constraint
This commit is contained in:
parent
557b9d88b5
commit
7497beefed
@ -215,6 +215,9 @@ class MatchFailedReason(Enum):
|
||||
DUPLICATE_NAME = auto()
|
||||
"""Two or more entities matched the same name constraint and could not be disambiguated."""
|
||||
|
||||
MULTIPLE_TARGETS = auto()
|
||||
"""Two or more entities matched when a single target is required."""
|
||||
|
||||
def is_no_entities_reason(self) -> bool:
|
||||
"""Return True if the match failed because no entities matched."""
|
||||
return self not in (
|
||||
@ -255,6 +258,9 @@ class MatchTargetsConstraints:
|
||||
allow_duplicate_names: bool = False
|
||||
"""True if entities with duplicate names are allowed in result."""
|
||||
|
||||
single_target: bool = False
|
||||
"""True if result must contain a single target."""
|
||||
|
||||
@property
|
||||
def has_constraints(self) -> bool:
|
||||
"""Returns True if at least one constraint is set (ignores assistant)."""
|
||||
@ -266,6 +272,7 @@ class MatchTargetsConstraints:
|
||||
or self.device_classes
|
||||
or self.features
|
||||
or self.states
|
||||
or self.single_target
|
||||
)
|
||||
|
||||
|
||||
@ -291,7 +298,7 @@ class MatchTargetsResult:
|
||||
"""Reason for failed match when is_match = False."""
|
||||
|
||||
states: list[State] = field(default_factory=list)
|
||||
"""List of matched entity states when is_match = True."""
|
||||
"""List of matched entity states."""
|
||||
|
||||
no_match_name: str | None = None
|
||||
"""Name of invalid area/floor or duplicate name when match fails for those reasons."""
|
||||
@ -357,7 +364,6 @@ class MatchTargetsCandidate:
|
||||
is_exposed: bool
|
||||
entity: entity_registry.RegistryEntry | None = None
|
||||
area: area_registry.AreaEntry | None = None
|
||||
floor: floor_registry.FloorEntry | None = None
|
||||
device: device_registry.DeviceEntry | None = None
|
||||
matched_name: str | None = None
|
||||
|
||||
@ -549,6 +555,7 @@ def async_match_targets( # noqa: C901
|
||||
or constraints.device_classes
|
||||
or constraints.area_name
|
||||
or constraints.floor_name
|
||||
or constraints.single_target
|
||||
):
|
||||
if constraints.assistant:
|
||||
# Check exposure
|
||||
@ -719,6 +726,48 @@ def async_match_targets( # noqa: C901
|
||||
|
||||
candidates = final_candidates
|
||||
|
||||
if constraints.single_target and len(candidates) > 1:
|
||||
# Find best match using preferences
|
||||
if not (preferences.area_id or preferences.floor_id):
|
||||
# No preferences
|
||||
return MatchTargetsResult(
|
||||
False,
|
||||
MatchFailedReason.MULTIPLE_TARGETS,
|
||||
states=[c.state for c in candidates],
|
||||
)
|
||||
|
||||
if not areas_added:
|
||||
ar = area_registry.async_get(hass)
|
||||
dr = device_registry.async_get(hass)
|
||||
_add_areas(ar, dr, candidates)
|
||||
areas_added = True
|
||||
|
||||
filtered_candidates: list[MatchTargetsCandidate] = candidates
|
||||
if preferences.area_id:
|
||||
# Filter by area
|
||||
filtered_candidates = [
|
||||
c for c in candidates if c.area and (c.area.id == preferences.area_id)
|
||||
]
|
||||
|
||||
if (len(filtered_candidates) > 1) and preferences.floor_id:
|
||||
# Filter by floor
|
||||
filtered_candidates = [
|
||||
c
|
||||
for c in candidates
|
||||
if c.area and (c.area.floor_id == preferences.floor_id)
|
||||
]
|
||||
|
||||
if len(filtered_candidates) != 1:
|
||||
# Filtering could not restrict to a single target
|
||||
return MatchTargetsResult(
|
||||
False,
|
||||
MatchFailedReason.MULTIPLE_TARGETS,
|
||||
states=[c.state for c in candidates],
|
||||
)
|
||||
|
||||
# Filtering succeeded
|
||||
candidates = filtered_candidates
|
||||
|
||||
return MatchTargetsResult(
|
||||
True,
|
||||
None,
|
||||
|
@ -234,7 +234,7 @@ async def test_async_match_targets(
|
||||
|
||||
# Floor 2
|
||||
floor_2 = floor_registry.async_create("second floor", aliases={"upstairs"})
|
||||
area_bedroom_2 = area_registry.async_get_or_create("bedroom")
|
||||
area_bedroom_2 = area_registry.async_get_or_create("second floor bedroom")
|
||||
area_bedroom_2 = area_registry.async_update(
|
||||
area_bedroom_2.id, floor_id=floor_2.floor_id
|
||||
)
|
||||
@ -269,7 +269,7 @@ async def test_async_match_targets(
|
||||
|
||||
# Floor 3
|
||||
floor_3 = floor_registry.async_create("third floor", aliases={"upstairs"})
|
||||
area_bedroom_3 = area_registry.async_get_or_create("bedroom")
|
||||
area_bedroom_3 = area_registry.async_get_or_create("third floor bedroom")
|
||||
area_bedroom_3 = area_registry.async_update(
|
||||
area_bedroom_3.id, floor_id=floor_3.floor_id
|
||||
)
|
||||
@ -510,6 +510,37 @@ async def test_async_match_targets(
|
||||
bathroom_light_3.entity_id,
|
||||
}
|
||||
|
||||
# Check single target constraint
|
||||
result = intent.async_match_targets(
|
||||
hass,
|
||||
intent.MatchTargetsConstraints(domains={"light"}, single_target=True),
|
||||
states=states,
|
||||
)
|
||||
assert not result.is_match
|
||||
assert result.no_match_reason == intent.MatchFailedReason.MULTIPLE_TARGETS
|
||||
|
||||
# Only one light on the ground floor
|
||||
result = intent.async_match_targets(
|
||||
hass,
|
||||
intent.MatchTargetsConstraints(domains={"light"}, single_target=True),
|
||||
preferences=intent.MatchTargetsPreferences(floor_id=floor_1.floor_id),
|
||||
states=states,
|
||||
)
|
||||
assert result.is_match
|
||||
assert len(result.states) == 1
|
||||
assert result.states[0].entity_id == bathroom_light_1.entity_id
|
||||
|
||||
# Only one switch in bedroom
|
||||
result = intent.async_match_targets(
|
||||
hass,
|
||||
intent.MatchTargetsConstraints(domains={"switch"}, single_target=True),
|
||||
preferences=intent.MatchTargetsPreferences(area_id=area_bedroom_2.id),
|
||||
states=states,
|
||||
)
|
||||
assert result.is_match
|
||||
assert len(result.states) == 1
|
||||
assert result.states[0].entity_id == bedroom_switch_2.entity_id
|
||||
|
||||
|
||||
async def test_match_device_area(
|
||||
hass: HomeAssistant,
|
||||
|
Loading…
x
Reference in New Issue
Block a user