Person: Use the home zone lat/lon coordinates when detected home by a stationary tracker (#134075)

Co-authored-by: Erik Montnemery <erik@montnemery.com>
This commit is contained in:
Sören Beye
2025-08-27 10:57:25 +02:00
committed by GitHub
parent 43a1a679f9
commit e894a03c43
3 changed files with 77 additions and 32 deletions

View File

@@ -15,6 +15,7 @@ from homeassistant.components.device_tracker import (
DOMAIN as DEVICE_TRACKER_DOMAIN,
SourceType,
)
from homeassistant.components.zone import ENTITY_ID_HOME
from homeassistant.const import (
ATTR_EDITABLE,
ATTR_GPS_ACCURACY,
@@ -464,7 +465,7 @@ class Person(
"""Register device trackers."""
await super().async_added_to_hass()
if state := await self.async_get_last_state():
self._parse_source_state(state)
self._parse_source_state(state, state)
if self.hass.is_running:
# Update person now if hass is already running.
@@ -514,7 +515,7 @@ class Person(
@callback
def _update_state(self) -> None:
"""Update the state."""
latest_non_gps_home = latest_not_home = latest_gps = latest = None
latest_non_gps_home = latest_not_home = latest_gps = latest = coordinates = None
for entity_id in self._config[CONF_DEVICE_TRACKERS]:
state = self.hass.states.get(entity_id)
@@ -530,13 +531,23 @@ class Person(
if latest_non_gps_home:
latest = latest_non_gps_home
if (
latest_non_gps_home.attributes.get(ATTR_LATITUDE) is None
and latest_non_gps_home.attributes.get(ATTR_LONGITUDE) is None
and (home_zone := self.hass.states.get(ENTITY_ID_HOME))
):
coordinates = home_zone
else:
coordinates = latest_non_gps_home
elif latest_gps:
latest = latest_gps
coordinates = latest_gps
else:
latest = latest_not_home
coordinates = latest_not_home
if latest:
self._parse_source_state(latest)
if latest and coordinates:
self._parse_source_state(latest, coordinates)
else:
self._attr_state = None
self._source = None
@@ -548,15 +559,15 @@ class Person(
self.async_write_ha_state()
@callback
def _parse_source_state(self, state: State) -> None:
def _parse_source_state(self, state: State, coordinates: State) -> None:
"""Parse source state and set person attributes.
This is a device tracker state or the restored person state.
"""
self._attr_state = state.state
self._source = state.entity_id
self._latitude = state.attributes.get(ATTR_LATITUDE)
self._longitude = state.attributes.get(ATTR_LONGITUDE)
self._latitude = coordinates.attributes.get(ATTR_LATITUDE)
self._longitude = coordinates.attributes.get(ATTR_LONGITUDE)
self._gps_accuracy = state.attributes.get(ATTR_GPS_ACCURACY)
@callback

View File

@@ -2,7 +2,7 @@
"domain": "person",
"name": "Person",
"codeowners": [],
"dependencies": ["image_upload", "http"],
"dependencies": ["image_upload", "http", "zone"],
"documentation": "https://www.home-assistant.io/integrations/person",
"integration_type": "system",
"iot_class": "calculated",

View File

@@ -14,7 +14,9 @@ from homeassistant.components.person import (
DOMAIN,
)
from homeassistant.const import (
ATTR_EDITABLE,
ATTR_ENTITY_PICTURE,
ATTR_FRIENDLY_NAME,
ATTR_GPS_ACCURACY,
ATTR_ID,
ATTR_LATITUDE,
@@ -112,14 +114,19 @@ async def test_setup_tracker(hass: HomeAssistant, hass_admin_user: MockUser) ->
}
assert await async_setup_component(hass, DOMAIN, config)
expected_attributes = {
ATTR_DEVICE_TRACKERS: [DEVICE_TRACKER],
ATTR_EDITABLE: False,
ATTR_FRIENDLY_NAME: "tracked person",
ATTR_ID: "1234",
ATTR_USER_ID: user_id,
}
state = hass.states.get("person.tracked_person")
assert state.state == STATE_UNKNOWN
assert state.attributes.get(ATTR_ID) == "1234"
assert state.attributes.get(ATTR_LATITUDE) is None
assert state.attributes.get(ATTR_LONGITUDE) is None
assert state.attributes.get(ATTR_SOURCE) is None
assert state.attributes.get(ATTR_USER_ID) == user_id
assert state.attributes == expected_attributes
# Test home without coordinates
hass.states.async_set(DEVICE_TRACKER, "home")
await hass.async_block_till_done()
@@ -131,13 +138,41 @@ async def test_setup_tracker(hass: HomeAssistant, hass_admin_user: MockUser) ->
state = hass.states.get("person.tracked_person")
assert state.state == "home"
assert state.attributes.get(ATTR_ID) == "1234"
assert state.attributes.get(ATTR_LATITUDE) is None
assert state.attributes.get(ATTR_LONGITUDE) is None
assert state.attributes.get(ATTR_SOURCE) == DEVICE_TRACKER
assert state.attributes.get(ATTR_USER_ID) == user_id
assert state.attributes.get(ATTR_DEVICE_TRACKERS) == [DEVICE_TRACKER]
assert state.attributes == expected_attributes | {
ATTR_LATITUDE: 32.87336,
ATTR_LONGITUDE: -117.22743,
ATTR_SOURCE: DEVICE_TRACKER,
}
# Test home with coordinates
hass.states.async_set(
DEVICE_TRACKER,
"home",
{ATTR_LATITUDE: 10.123456, ATTR_LONGITUDE: 11.123456, ATTR_GPS_ACCURACY: 10},
)
await hass.async_block_till_done()
state = hass.states.get("person.tracked_person")
assert state.state == "home"
assert state.attributes == expected_attributes | {
ATTR_GPS_ACCURACY: 10,
ATTR_LATITUDE: 10.123456,
ATTR_LONGITUDE: 11.123456,
ATTR_SOURCE: DEVICE_TRACKER,
}
# Test not_home without coordinates
hass.states.async_set(
DEVICE_TRACKER,
"not_home",
)
await hass.async_block_till_done()
state = hass.states.get("person.tracked_person")
assert state.state == "not_home"
assert state.attributes == expected_attributes | {ATTR_SOURCE: DEVICE_TRACKER}
# Test not_home with coordinates
hass.states.async_set(
DEVICE_TRACKER,
"not_home",
@@ -147,13 +182,12 @@ async def test_setup_tracker(hass: HomeAssistant, hass_admin_user: MockUser) ->
state = hass.states.get("person.tracked_person")
assert state.state == "not_home"
assert state.attributes.get(ATTR_ID) == "1234"
assert state.attributes.get(ATTR_LATITUDE) == 10.123456
assert state.attributes.get(ATTR_LONGITUDE) == 11.123456
assert state.attributes.get(ATTR_GPS_ACCURACY) == 10
assert state.attributes.get(ATTR_SOURCE) == DEVICE_TRACKER
assert state.attributes.get(ATTR_USER_ID) == user_id
assert state.attributes.get(ATTR_DEVICE_TRACKERS) == [DEVICE_TRACKER]
assert state.attributes == expected_attributes | {
ATTR_GPS_ACCURACY: 10,
ATTR_LATITUDE: 10.123456,
ATTR_LONGITUDE: 11.123456,
ATTR_SOURCE: DEVICE_TRACKER,
}
async def test_setup_two_trackers(
@@ -188,8 +222,8 @@ async def test_setup_two_trackers(
state = hass.states.get("person.tracked_person")
assert state.state == "home"
assert state.attributes.get(ATTR_ID) == "1234"
assert state.attributes.get(ATTR_LATITUDE) is None
assert state.attributes.get(ATTR_LONGITUDE) is None
assert state.attributes.get(ATTR_LATITUDE) == 32.87336
assert state.attributes.get(ATTR_LONGITUDE) == -117.22743
assert state.attributes.get(ATTR_GPS_ACCURACY) is None
assert state.attributes.get(ATTR_SOURCE) == DEVICE_TRACKER
assert state.attributes.get(ATTR_USER_ID) == user_id
@@ -453,8 +487,8 @@ async def test_load_person_storage(
state = hass.states.get("person.tracked_person")
assert state.state == "home"
assert state.attributes.get(ATTR_ID) == "1234"
assert state.attributes.get(ATTR_LATITUDE) is None
assert state.attributes.get(ATTR_LONGITUDE) is None
assert state.attributes.get(ATTR_LATITUDE) == 32.87336
assert state.attributes.get(ATTR_LONGITUDE) == -117.22743
assert state.attributes.get(ATTR_SOURCE) == DEVICE_TRACKER
assert state.attributes.get(ATTR_USER_ID) == hass_admin_user.id
@@ -817,7 +851,7 @@ async def test_reload(hass: HomeAssistant, hass_admin_user: MockUser) -> None:
},
)
assert len(hass.states.async_entity_ids()) == 2
assert len(hass.states.async_entity_ids()) == 3 # Person1, Person2, zone.home
state_1 = hass.states.get("person.person_1")
state_2 = hass.states.get("person.person_2")
@@ -847,7 +881,7 @@ async def test_reload(hass: HomeAssistant, hass_admin_user: MockUser) -> None:
)
await hass.async_block_till_done()
assert len(hass.states.async_entity_ids()) == 2
assert len(hass.states.async_entity_ids()) == 3 # Person1, Person2, zone.home
state_1 = hass.states.get("person.person_1")
state_2 = hass.states.get("person.person_2")