Home Assistant Unofficial Reference 2024.12.1
location.py
Go to the documentation of this file.
1 """Location helpers for Home Assistant."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Iterable
6 import logging
7 
8 from homeassistant.const import ATTR_LATITUDE, ATTR_LONGITUDE
9 from homeassistant.core import HomeAssistant, State
10 from homeassistant.util import location as loc_util
11 
12 _LOGGER = logging.getLogger(__name__)
13 
14 
15 def has_location(state: State) -> bool:
16  """Test if state contains a valid location.
17 
18  Async friendly.
19  """
20  return (
21  isinstance(state, State)
22  and isinstance(state.attributes.get(ATTR_LATITUDE), float)
23  and isinstance(state.attributes.get(ATTR_LONGITUDE), float)
24  )
25 
26 
27 def closest(latitude: float, longitude: float, states: Iterable[State]) -> State | None:
28  """Return closest state to point.
29 
30  Async friendly.
31  """
32  with_location = [state for state in states if has_location(state)]
33 
34  if not with_location:
35  return None
36 
37  return min(
38  with_location,
39  key=lambda state: loc_util.distance(
40  state.attributes.get(ATTR_LATITUDE),
41  state.attributes.get(ATTR_LONGITUDE),
42  latitude,
43  longitude,
44  )
45  or 0,
46  )
47 
48 
50  hass: HomeAssistant, name: str, recursion_history: list | None = None
51 ) -> str | None:
52  """Try to resolve the a location from a supplied name or entity_id.
53 
54  Will recursively resolve an entity if pointed to by the state of the supplied
55  entity.
56 
57  Returns coordinates in the form of '90.000,180.000', an address or
58  the state of the last resolved entity.
59  """
60  # Check if a friendly name of a zone was supplied
61  if (zone_coords := resolve_zone(hass, name)) is not None:
62  return zone_coords
63 
64  # Check if an entity_id was supplied.
65  if (entity_state := hass.states.get(name)) is None:
66  _LOGGER.debug("Unable to find entity %s", name)
67  return name
68 
69  # Check if the entity_state has location attributes
70  if has_location(entity_state):
71  return _get_location_from_attributes(entity_state)
72 
73  # Check if entity_state is a zone
74  zone_entity = hass.states.get(f"zone.{entity_state.state}")
75  if has_location(zone_entity): # type: ignore[arg-type]
76  _LOGGER.debug(
77  "%s is in %s, getting zone location",
78  name,
79  zone_entity.entity_id, # type: ignore[union-attr]
80  )
81  return _get_location_from_attributes(zone_entity) # type: ignore[arg-type]
82 
83  # Check if entity_state is a friendly name of a zone
84  if (zone_coords := resolve_zone(hass, entity_state.state)) is not None:
85  return zone_coords
86 
87  # Check if entity_state is an entity_id
88  if recursion_history is None:
89  recursion_history = []
90  recursion_history.append(name)
91  if entity_state.state in recursion_history:
92  _LOGGER.error(
93  (
94  "Circular reference detected while trying to find coordinates of an"
95  " entity. The state of %s has already been checked"
96  ),
97  entity_state.state,
98  )
99  return None
100  _LOGGER.debug("Getting nested entity for state: %s", entity_state.state)
101  nested_entity = hass.states.get(entity_state.state)
102  if nested_entity is not None:
103  _LOGGER.debug("Resolving nested entity_id: %s", entity_state.state)
104  return find_coordinates(hass, entity_state.state, recursion_history)
105 
106  # Might be an address, coordinates or anything else.
107  # This has to be checked by the caller.
108  return entity_state.state
109 
110 
111 def resolve_zone(hass: HomeAssistant, zone_name: str) -> str | None:
112  """Get a lat/long from a zones friendly_name.
113 
114  None is returned if no zone is found by that friendly_name.
115  """
116  states = hass.states.async_all("zone")
117  for state in states:
118  if state.name == zone_name:
119  return _get_location_from_attributes(state)
120 
121  return None
122 
123 
124 def _get_location_from_attributes(entity_state: State) -> str:
125  """Get the lat/long string from an entities attributes."""
126  attr = entity_state.attributes
127  return f"{attr.get(ATTR_LATITUDE)},{attr.get(ATTR_LONGITUDE)}"
str _get_location_from_attributes(State entity_state)
Definition: location.py:124
bool has_location(State state)
Definition: location.py:15
State|None closest(float latitude, float longitude, Iterable[State] states)
Definition: location.py:27
str|None find_coordinates(HomeAssistant hass, str name, list|None recursion_history=None)
Definition: location.py:51
str|None resolve_zone(HomeAssistant hass, str zone_name)
Definition: location.py:111