From d78c05ab62baea8a06f19bc7d06e60ed0e7435cc Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Thu, 2 Apr 2026 14:09:57 +0200 Subject: [PATCH] Propagate the in_zones attribute from device trackers in person entities (#167192) --- homeassistant/components/person/__init__.py | 5 +++++ tests/components/person/test_init.py | 25 +++++++++++++++++---- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/person/__init__.py b/homeassistant/components/person/__init__.py index 610d77aefd2..2fc04785812 100644 --- a/homeassistant/components/person/__init__.py +++ b/homeassistant/components/person/__init__.py @@ -11,6 +11,7 @@ import voluptuous as vol from homeassistant.auth import EVENT_USER_REMOVED from homeassistant.components import persistent_notification, websocket_api from homeassistant.components.device_tracker import ( + ATTR_IN_ZONES, ATTR_SOURCE_TYPE, DOMAIN as DEVICE_TRACKER_DOMAIN, SourceType, @@ -435,6 +436,7 @@ class Person( self._unsub_track_device: Callable[[], None] | None = None self._attr_state: str | None = None self.device_trackers: list[str] = [] + self._in_zones: list[str] = [] self._attr_unique_id = config[CONF_ID] self._set_attrs_from_config() @@ -552,6 +554,7 @@ class Person( self._latitude = None self._longitude = None self._gps_accuracy = None + self._in_zones = [] self._update_extra_state_attributes() self.async_write_ha_state() @@ -567,6 +570,7 @@ class Person( self._latitude = coordinates.attributes.get(ATTR_LATITUDE) self._longitude = coordinates.attributes.get(ATTR_LONGITUDE) self._gps_accuracy = coordinates.attributes.get(ATTR_GPS_ACCURACY) + self._in_zones = coordinates.attributes.get(ATTR_IN_ZONES, []) @callback def _update_extra_state_attributes(self) -> None: @@ -575,6 +579,7 @@ class Person( ATTR_EDITABLE: self.editable, ATTR_ID: self.unique_id, ATTR_DEVICE_TRACKERS: self.device_trackers, + ATTR_IN_ZONES: self._in_zones, } if self._latitude is not None: diff --git a/tests/components/person/test_init.py b/tests/components/person/test_init.py index a4ddf45d101..9e2f26e0862 100644 --- a/tests/components/person/test_init.py +++ b/tests/components/person/test_init.py @@ -6,7 +6,11 @@ from unittest.mock import patch import pytest from homeassistant.components import person -from homeassistant.components.device_tracker import ATTR_SOURCE_TYPE, SourceType +from homeassistant.components.device_tracker import ( + ATTR_IN_ZONES, + ATTR_SOURCE_TYPE, + SourceType, +) from homeassistant.components.person import ( ATTR_DEVICE_TRACKERS, ATTR_SOURCE, @@ -119,6 +123,7 @@ async def test_setup_tracker(hass: HomeAssistant, hass_admin_user: MockUser) -> ATTR_EDITABLE: False, ATTR_FRIENDLY_NAME: "tracked person", ATTR_ID: "1234", + ATTR_IN_ZONES: [], ATTR_USER_ID: user_id, } @@ -223,10 +228,17 @@ async def test_setup_two_trackers( # gps_accuracy, but we want to assert that the person entity uses latitude # longitude and accuracy from the home zone, not from the state attributes # of the device tracker. + # Router tracker at home — person gets coordinates from the home zone, + # not from the router tracker. The router tracker has gps_accuracy=99 + # and in_zones=["zone.fake"] to verify these are NOT propagated. hass.states.async_set( DEVICE_TRACKER, "home", - {ATTR_SOURCE_TYPE: SourceType.ROUTER, ATTR_GPS_ACCURACY: 99}, + { + ATTR_SOURCE_TYPE: SourceType.ROUTER, + ATTR_GPS_ACCURACY: 99, + ATTR_IN_ZONES: ["zone.fake"], + }, ) await hass.async_block_till_done() @@ -235,9 +247,10 @@ async def test_setup_two_trackers( assert state.attributes.get(ATTR_ID) == "1234" assert state.attributes.get(ATTR_LATITUDE) == 32.87336 assert state.attributes.get(ATTR_LONGITUDE) == -117.22743 - # GPS accuracy comes from the coordinates source (home zone), not from - # the state source (router tracker which reported gps_accuracy=99). + # GPS accuracy and in_zones come from the coordinates source (home zone), + # not from the state source (router tracker). assert state.attributes.get(ATTR_GPS_ACCURACY) is None + assert state.attributes.get(ATTR_IN_ZONES) == [] assert state.attributes.get(ATTR_SOURCE) == DEVICE_TRACKER assert state.attributes.get(ATTR_USER_ID) == user_id assert state.attributes.get(ATTR_DEVICE_TRACKERS) == [ @@ -252,6 +265,7 @@ async def test_setup_two_trackers( ATTR_LATITUDE: 12.123456, ATTR_LONGITUDE: 13.123456, ATTR_GPS_ACCURACY: 12, + ATTR_IN_ZONES: ["zone.work"], ATTR_SOURCE_TYPE: SourceType.GPS, }, ) @@ -267,6 +281,7 @@ async def test_setup_two_trackers( assert state.attributes.get(ATTR_LATITUDE) == 12.123456 assert state.attributes.get(ATTR_LONGITUDE) == 13.123456 assert state.attributes.get(ATTR_GPS_ACCURACY) == 12 + assert state.attributes.get(ATTR_IN_ZONES) == ["zone.work"] assert state.attributes.get(ATTR_SOURCE) == DEVICE_TRACKER_2 assert state.attributes.get(ATTR_USER_ID) == user_id assert state.attributes.get(ATTR_DEVICE_TRACKERS) == [ @@ -346,6 +361,7 @@ async def test_setup_router_ble_trackers( ATTR_LATITUDE: 12.123456, ATTR_LONGITUDE: 13.123456, ATTR_GPS_ACCURACY: 12, + ATTR_IN_ZONES: ["zone.office"], ATTR_SOURCE_TYPE: SourceType.BLUETOOTH_LE, }, ) @@ -358,6 +374,7 @@ async def test_setup_router_ble_trackers( assert state.attributes.get(ATTR_LATITUDE) == 12.123456 assert state.attributes.get(ATTR_LONGITUDE) == 13.123456 assert state.attributes.get(ATTR_GPS_ACCURACY) == 12 + assert state.attributes.get(ATTR_IN_ZONES) == ["zone.office"] assert state.attributes.get(ATTR_SOURCE) == DEVICE_TRACKER_2 assert state.attributes.get(ATTR_USER_ID) == user_id assert state.attributes.get(ATTR_DEVICE_TRACKERS) == [