Compare commits

...

6 Commits

Author SHA1 Message Date
Erik
002bf80a1f Update tests 2026-03-31 10:17:02 +02:00
Erik
8acc96b9ba Update snapshots 2026-03-31 10:16:14 +02:00
Erik
db379cb79f Adjust 2026-03-31 09:18:29 +02:00
Erik
af2899f22a Revert changes in legacy device tracker 2026-03-30 10:28:47 +02:00
Erik
8a13da4e9d Avoid calling zone.async_active_zones twice 2026-03-26 20:20:48 +01:00
Erik
3bd7e93285 Add new state attribute in_zones to device_tracker 2026-03-26 09:05:03 +01:00
22 changed files with 209 additions and 41 deletions

View File

@@ -21,6 +21,7 @@ from .const import ( # noqa: F401
ATTR_DEV_ID,
ATTR_GPS,
ATTR_HOST_NAME,
ATTR_IN_ZONES,
ATTR_IP,
ATTR_LOCATION_NAME,
ATTR_MAC,

View File

@@ -3,7 +3,7 @@
from __future__ import annotations
import asyncio
from typing import final
from typing import Any, final
from propcache.api import cached_property
@@ -18,7 +18,7 @@ from homeassistant.const import (
STATE_NOT_HOME,
EntityCategory,
)
from homeassistant.core import Event, HomeAssistant, callback
from homeassistant.core import Event, HomeAssistant, State, callback
from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.helpers.device_registry import (
DeviceInfo,
@@ -33,6 +33,7 @@ from homeassistant.util.hass_dict import HassKey
from .const import (
ATTR_HOST_NAME,
ATTR_IN_ZONES,
ATTR_IP,
ATTR_MAC,
ATTR_SOURCE_TYPE,
@@ -223,6 +224,9 @@ class TrackerEntity(
_attr_longitude: float | None = None
_attr_source_type: SourceType = SourceType.GPS
__active_zone: State | None = None
__in_zones: list[str] | None = None
@cached_property
def should_poll(self) -> bool:
"""No polling for entities that have location pushed."""
@@ -256,6 +260,18 @@ class TrackerEntity(
"""Return longitude value of the device."""
return self._attr_longitude
@callback
def _async_write_ha_state(self) -> None:
"""Calculate active zones."""
if self.latitude is not None and self.longitude is not None:
self.__active_zone, self.__in_zones = zone.async_in_zones(
self.hass, self.latitude, self.longitude, self.location_accuracy
)
else:
self.__active_zone = None
self.__in_zones = None
super()._async_write_ha_state()
@property
def state(self) -> str | None:
"""Return the state of the device."""
@@ -263,9 +279,7 @@ class TrackerEntity(
return self.location_name
if self.latitude is not None and self.longitude is not None:
zone_state = zone.async_active_zone(
self.hass, self.latitude, self.longitude, self.location_accuracy
)
zone_state = self.__active_zone
if zone_state is None:
state = STATE_NOT_HOME
elif zone_state.entity_id == zone.ENTITY_ID_HOME:
@@ -278,12 +292,13 @@ class TrackerEntity(
@final
@property
def state_attributes(self) -> dict[str, StateType]:
def state_attributes(self) -> dict[str, Any]:
"""Return the device state attributes."""
attr: dict[str, StateType] = {}
attr: dict[str, Any] = {ATTR_IN_ZONES: []}
attr.update(super().state_attributes)
if self.latitude is not None and self.longitude is not None:
attr[ATTR_IN_ZONES] = self.__in_zones or []
attr[ATTR_LATITUDE] = self.latitude
attr[ATTR_LONGITUDE] = self.longitude
attr[ATTR_GPS_ACCURACY] = self.location_accuracy

View File

@@ -43,6 +43,7 @@ ATTR_BATTERY: Final = "battery"
ATTR_DEV_ID: Final = "dev_id"
ATTR_GPS: Final = "gps"
ATTR_HOST_NAME: Final = "host_name"
ATTR_IN_ZONES: Final = "in_zones"
ATTR_LOCATION_NAME: Final = "location_name"
ATTR_MAC: Final = "mac"
ATTR_SOURCE_TYPE: Final = "source_type"

View File

@@ -113,17 +113,21 @@ DATA_ZONE_STORAGE_COLLECTION: HassKey[ZoneStorageCollection] = HassKey(DOMAIN)
DATA_ZONE_ENTITY_IDS: HassKey[list[str]] = HassKey(ZONE_ENTITY_IDS)
@bind_hass
def async_active_zone(
def async_in_zones(
hass: HomeAssistant, latitude: float, longitude: float, radius: float = 0
) -> State | None:
"""Find the active zone for given latitude, longitude.
) -> tuple[State | None, list[str]]:
"""Find zones which contain the given latitude and longitude.
Returns a tuple of the closest active zone and a list of all zones which
contain the given latitude and longitude. The list of zones is sorted by
distance and then by radius so that the closest and smallest zone is first.
This method must be run in the event loop.
"""
# Sort entity IDs so that we are deterministic if equal distance to 2 zones
min_dist: float = sys.maxsize
closest: State | None = None
zones: list[tuple[str, float, float]] = []
# This can be called before async_setup by device tracker
zone_entity_ids = hass.data.get(DATA_ZONE_ENTITY_IDS, ())
@@ -133,10 +137,12 @@ def async_active_zone(
not (zone := hass.states.get(entity_id))
# Skip unavailable zones
or zone.state == STATE_UNAVAILABLE
# Skip passive zones
or (zone_attrs := zone.attributes).get(ATTR_PASSIVE)
):
continue
zone_attrs = zone.attributes
if (
# Skip zones where we cannot calculate distance
or (
(
zone_dist := distance(
latitude,
longitude,
@@ -151,6 +157,12 @@ def async_active_zone(
):
continue
zones.append((zone.entity_id, zone_dist, zone_radius))
# Skip passive zones
if zone_attrs.get(ATTR_PASSIVE):
continue
# If have a closest and its not closer than the closest skip it
if closest and not (
zone_dist < min_dist
@@ -166,7 +178,20 @@ def async_active_zone(
min_dist = zone_dist
closest = zone
return closest
# Sort by distance and then by radius so the closest and smallest zone is first.
zones.sort(key=lambda x: (x[1], x[2]))
return (closest, [itm[0] for itm in zones])
@bind_hass
def async_active_zone(
hass: HomeAssistant, latitude: float, longitude: float, radius: float = 0
) -> State | None:
"""Find the active zone for given latitude, longitude.
This method must be run in the event loop.
"""
return async_in_zones(hass, latitude, longitude, radius)[0]
@callback

View File

@@ -42,6 +42,8 @@
'friendly_name': 'Test Vehicle',
'gps_accuracy': 6.0,
'icon': 'mdi:car',
'in_zones': list([
]),
'latitude': 50.1109221,
'longitude': 8.6821267,
'source_type': <SourceType.GPS: 'gps'>,

View File

@@ -7,6 +7,7 @@ import pytest
from homeassistant.components.device_tracker import (
ATTR_HOST_NAME,
ATTR_IN_ZONES,
ATTR_IP,
ATTR_MAC,
ATTR_SOURCE_TYPE,
@@ -384,6 +385,7 @@ async def test_load_unload_entry(
{
ATTR_SOURCE_TYPE: SourceType.GPS,
ATTR_GPS_ACCURACY: 0,
ATTR_IN_ZONES: [],
ATTR_LATITUDE: 1.0,
ATTR_LONGITUDE: 2.0,
},
@@ -397,6 +399,7 @@ async def test_load_unload_entry(
{
ATTR_SOURCE_TYPE: SourceType.GPS,
ATTR_GPS_ACCURACY: 0,
ATTR_IN_ZONES: ["zone.home"],
ATTR_LATITUDE: 50.0,
ATTR_LONGITUDE: 60.0,
},
@@ -410,6 +413,7 @@ async def test_load_unload_entry(
{
ATTR_SOURCE_TYPE: SourceType.GPS,
ATTR_GPS_ACCURACY: 0,
ATTR_IN_ZONES: ["zone.other_zone", "zone.other_zone_larger"],
ATTR_LATITUDE: -50.0,
ATTR_LONGITUDE: -60.0,
},
@@ -422,6 +426,7 @@ async def test_load_unload_entry(
"zen_zone",
{
ATTR_SOURCE_TYPE: SourceType.GPS,
ATTR_IN_ZONES: [],
},
),
(
@@ -430,7 +435,10 @@ async def test_load_unload_entry(
None,
None,
STATE_UNKNOWN,
{ATTR_SOURCE_TYPE: SourceType.GPS},
{
ATTR_SOURCE_TYPE: SourceType.GPS,
ATTR_IN_ZONES: [],
},
),
(
100,
@@ -438,7 +446,11 @@ async def test_load_unload_entry(
None,
None,
STATE_UNKNOWN,
{ATTR_BATTERY_LEVEL: 100, ATTR_SOURCE_TYPE: SourceType.GPS},
{
ATTR_BATTERY_LEVEL: 100,
ATTR_SOURCE_TYPE: SourceType.GPS,
ATTR_IN_ZONES: [],
},
),
],
)
@@ -463,6 +475,11 @@ async def test_tracker_entity_state(
"0",
{ATTR_LATITUDE: -50.0, ATTR_LONGITUDE: -60.0, ATTR_RADIUS: 300},
)
hass.states.async_set(
"zone.other_zone_larger",
"0",
{ATTR_LATITUDE: -50.0, ATTR_LONGITUDE: -60.0, ATTR_RADIUS: 500},
)
await hass.async_block_till_done()
# Write state again to ensure the zone state is taken into account.
tracker_entity.async_write_ha_state()

View File

@@ -42,6 +42,8 @@
'entity_picture': 'http://res.cloudinary.com/iot-venture/image/upload/v1717594357/kyaqq7nfitrdvaoakb8s.jpg',
'friendly_name': 'Fluffy',
'gps_accuracy': 10.0,
'in_zones': list([
]),
'latitude': 52.520008,
'longitude': 13.404954,
'source_type': <SourceType.GPS: 'gps'>,

View File

@@ -41,6 +41,8 @@
'attributes': ReadOnlyDict({
'friendly_name': 'Test Mower 1',
'gps_accuracy': 0,
'in_zones': list([
]),
'latitude': 35.5402913,
'longitude': -82.5527055,
'source_type': <SourceType.GPS: 'gps'>,

View File

@@ -41,6 +41,8 @@
'attributes': ReadOnlyDict({
'friendly_name': 'mock model',
'gps_accuracy': 0,
'in_zones': list([
]),
'latitude': 25.0,
'longitude': -71.0,
'source_type': <SourceType.GPS: 'gps'>,

View File

@@ -41,6 +41,8 @@
'attributes': ReadOnlyDict({
'friendly_name': '2021 Honda Accord',
'gps_accuracy': 10,
'in_zones': list([
]),
'latitude': 37.7749,
'longitude': -122.4194,
'source_type': <SourceType.GPS: 'gps'>,

View File

@@ -51,53 +51,83 @@ async def setup_zone(hass: HomeAssistant) -> None:
# Send coordinates + location_name: Location name has precedence
(
{"gps": [10, 20], "location_name": "home"},
{"latitude": 10, "longitude": 20, "gps_accuracy": 30},
{
"latitude": 10,
"longitude": 20,
"gps_accuracy": 30,
"in_zones": ["zone.home"],
},
"home",
),
(
{"gps": [20, 30], "location_name": "office"},
{"latitude": 20, "longitude": 30, "gps_accuracy": 30},
{
"latitude": 20,
"longitude": 30,
"gps_accuracy": 30,
"in_zones": ["zone.office"],
},
"Office",
),
(
{"gps": [30, 40], "location_name": "school"},
{"latitude": 30, "longitude": 40, "gps_accuracy": 30},
{
"latitude": 30,
"longitude": 40,
"gps_accuracy": 30,
"in_zones": ["zone.school"],
},
"School",
),
# Send wrong coordinates + location_name: Location name has precedence
(
{"gps": [10, 10], "location_name": "home"},
{"latitude": 10, "longitude": 10, "gps_accuracy": 30},
{"latitude": 10, "longitude": 10, "gps_accuracy": 30, "in_zones": []},
"home",
),
(
{"gps": [10, 10], "location_name": "office"},
{"latitude": 10, "longitude": 10, "gps_accuracy": 30},
{"latitude": 10, "longitude": 10, "gps_accuracy": 30, "in_zones": []},
"Office",
),
(
{"gps": [10, 10], "location_name": "school"},
{"latitude": 10, "longitude": 10, "gps_accuracy": 30},
{"latitude": 10, "longitude": 10, "gps_accuracy": 30, "in_zones": []},
"School",
),
# Send location_name only
({"location_name": "home"}, {}, "home"),
({"location_name": "office"}, {}, "Office"),
({"location_name": "school"}, {}, "School"),
({"location_name": "home"}, {"in_zones": []}, "home"),
({"location_name": "office"}, {"in_zones": []}, "Office"),
({"location_name": "school"}, {"in_zones": []}, "School"),
# Send coordinates only - location is determined by coordinates
(
{"gps": [10, 20]},
{"latitude": 10, "longitude": 20, "gps_accuracy": 30},
{
"latitude": 10,
"longitude": 20,
"gps_accuracy": 30,
"in_zones": ["zone.home"],
},
"home",
),
(
{"gps": [20, 30]},
{"latitude": 20, "longitude": 30, "gps_accuracy": 30},
{
"latitude": 20,
"longitude": 30,
"gps_accuracy": 30,
"in_zones": ["zone.office"],
},
"Office",
),
(
{"gps": [30, 40]},
{"latitude": 30, "longitude": 40, "gps_accuracy": 30},
{
"latitude": 30,
"longitude": 40,
"gps_accuracy": 30,
"in_zones": ["zone.school"],
},
"School",
),
],
@@ -180,6 +210,7 @@ async def test_sending_location(
"course": 6,
"speed": 7,
"vertical_accuracy": 8,
"in_zones": [],
}

View File

@@ -239,6 +239,7 @@ async def test_redact_diagnostics(
"state": {
"attributes": {
"gps_accuracy": 1.5,
"in_zones": ["zone.home"],
"latitude": "**REDACTED**",
"longitude": "**REDACTED**",
"source_type": "gps",

View File

@@ -41,6 +41,8 @@
'attributes': ReadOnlyDict({
'friendly_name': 'NRGkick Test GPS tracker',
'gps_accuracy': 1.5,
'in_zones': list([
]),
'latitude': 47.0748,
'longitude': 15.4376,
'source_type': <SourceType.GPS: 'gps'>,

View File

@@ -40,6 +40,8 @@
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'REG-ZOE-50 Location',
'in_zones': list([
]),
'source_type': <SourceType.GPS: 'gps'>,
}),
'context': <ANY>,
@@ -142,6 +144,8 @@
'attributes': ReadOnlyDict({
'friendly_name': 'REG-CAPTUR-FUEL Location',
'gps_accuracy': 0,
'in_zones': list([
]),
'latitude': 48.1234567,
'longitude': 11.1234567,
'source_type': <SourceType.GPS: 'gps'>,
@@ -196,6 +200,8 @@
'attributes': ReadOnlyDict({
'friendly_name': 'REG-CAPTUR_PHEV Location',
'gps_accuracy': 0,
'in_zones': list([
]),
'latitude': 48.1234567,
'longitude': 11.1234567,
'source_type': <SourceType.GPS: 'gps'>,
@@ -250,6 +256,8 @@
'attributes': ReadOnlyDict({
'friendly_name': 'REG-MEG-0 Location',
'gps_accuracy': 0,
'in_zones': list([
]),
'latitude': 48.1234567,
'longitude': 11.1234567,
'source_type': <SourceType.GPS: 'gps'>,
@@ -304,6 +312,8 @@
'attributes': ReadOnlyDict({
'friendly_name': 'REG-TWINGO-III Location',
'gps_accuracy': 0,
'in_zones': list([
]),
'latitude': 48.1234567,
'longitude': 11.1234567,
'source_type': <SourceType.GPS: 'gps'>,
@@ -358,6 +368,8 @@
'attributes': ReadOnlyDict({
'friendly_name': 'REG-ZOE-50 Location',
'gps_accuracy': 0,
'in_zones': list([
]),
'latitude': 48.1234567,
'longitude': 11.1234567,
'source_type': <SourceType.GPS: 'gps'>,

View File

@@ -41,6 +41,8 @@
'attributes': ReadOnlyDict({
'friendly_name': 'Test Location',
'gps_accuracy': 0,
'in_zones': list([
]),
'latitude': -30.222626,
'longitude': -97.6236871,
'source_type': <SourceType.GPS: 'gps'>,
@@ -95,6 +97,8 @@
'attributes': ReadOnlyDict({
'friendly_name': 'Test Route',
'gps_accuracy': 0,
'in_zones': list([
]),
'latitude': 30.2226265,
'longitude': -97.6236871,
'source_type': <SourceType.GPS: 'gps'>,

View File

@@ -41,6 +41,8 @@
'attributes': ReadOnlyDict({
'friendly_name': 'Test Location',
'gps_accuracy': 0,
'in_zones': list([
]),
'latitude': -30.222626,
'longitude': -97.6236871,
'source_type': <SourceType.GPS: 'gps'>,
@@ -95,6 +97,8 @@
'attributes': ReadOnlyDict({
'friendly_name': 'Test Route',
'gps_accuracy': 0,
'in_zones': list([
]),
'latitude': 30.2226265,
'longitude': -97.6236871,
'source_type': <SourceType.GPS: 'gps'>,
@@ -112,6 +116,8 @@
'attributes': ReadOnlyDict({
'friendly_name': 'Test Location',
'gps_accuracy': 0,
'in_zones': list([
]),
'latitude': -30.222626,
'longitude': -97.6236871,
'source_type': <SourceType.GPS: 'gps'>,
@@ -129,6 +135,8 @@
'attributes': ReadOnlyDict({
'friendly_name': 'Test Route',
'gps_accuracy': 0,
'in_zones': list([
]),
'latitude': 30.2226265,
'longitude': -97.6236871,
'source_type': <SourceType.GPS: 'gps'>,

View File

@@ -42,6 +42,8 @@
'friendly_name': 'Test Location',
'gps_accuracy': 0,
'heading': 185,
'in_zones': list([
]),
'latitude': -30.222626,
'longitude': -97.6236871,
'source_type': <SourceType.GPS: 'gps'>,
@@ -97,6 +99,8 @@
'attributes': ReadOnlyDict({
'friendly_name': 'Test Route',
'gps_accuracy': 0,
'in_zones': list([
]),
'latitude': 30.2226265,
'longitude': -97.6236871,
'source_type': <SourceType.GPS: 'gps'>,

View File

@@ -42,6 +42,8 @@
'altitude': 0,
'friendly_name': 'Wallet',
'gps_accuracy': 13.496111,
'in_zones': list([
]),
'is_lost': False,
'last_lost_timestamp': datetime.datetime(1970, 1, 1, 3, 0, tzinfo=datetime.timezone.utc),
'last_timestamp': datetime.datetime(2020, 8, 13, 0, 55, 26, tzinfo=datetime.timezone.utc),

View File

@@ -103,6 +103,8 @@
'custom_attr_1': 'custom_attr_1_value',
'friendly_name': 'X-Wing',
'gps_accuracy': 3.5,
'in_zones': list([
]),
'latitude': '**REDACTED**',
'longitude': '**REDACTED**',
'source_type': 'gps',
@@ -397,6 +399,8 @@
'custom_attr_1': 'custom_attr_1_value',
'friendly_name': 'X-Wing',
'gps_accuracy': 3.5,
'in_zones': list([
]),
'latitude': '**REDACTED**',
'longitude': '**REDACTED**',
'source_type': 'gps',

View File

@@ -42,6 +42,8 @@
'battery_level': 88,
'friendly_name': 'Test Pet Tracker',
'gps_accuracy': 99,
'in_zones': list([
]),
'latitude': 22.333,
'longitude': 44.555,
'source_type': <SourceType.GPS: 'gps'>,

View File

@@ -41,6 +41,8 @@
'attributes': ReadOnlyDict({
'friendly_name': 'Volvo EX30 Location',
'gps_accuracy': 0,
'in_zones': list([
]),
'latitude': 57.72537482589284,
'longitude': 11.849843629550225,
'source_type': <SourceType.GPS: 'gps'>,
@@ -95,6 +97,8 @@
'attributes': ReadOnlyDict({
'friendly_name': 'Volvo S90 Location',
'gps_accuracy': 0,
'in_zones': list([
]),
'latitude': 57.72537482589284,
'longitude': 11.849843629550225,
'source_type': <SourceType.GPS: 'gps'>,
@@ -149,6 +153,8 @@
'attributes': ReadOnlyDict({
'friendly_name': 'Volvo XC40 Location',
'gps_accuracy': 0,
'in_zones': list([
]),
'latitude': 57.72537482589284,
'longitude': 11.849843629550225,
'source_type': <SourceType.GPS: 'gps'>,
@@ -203,6 +209,8 @@
'attributes': ReadOnlyDict({
'friendly_name': 'Volvo XC90 Location',
'gps_accuracy': 0,
'in_zones': list([
]),
'latitude': 57.72537482589284,
'longitude': 11.849843629550225,
'source_type': <SourceType.GPS: 'gps'>,

View File

@@ -126,8 +126,11 @@ async def test_active_zone_skips_passive_zones(hass: HomeAssistant) -> None:
},
)
await hass.async_block_till_done()
active = zone.async_active_zone(hass, 32.880600, -117.237561)
assert active is None
active_zone = zone.async_active_zone(hass, 32.880600, -117.237561)
assert active_zone is None
active_zone, in_zones = zone.async_in_zones(hass, 32.880600, -117.237561)
assert active_zone is None
assert in_zones == ["zone.passive_zone"]
async def test_active_zone_skips_passive_zones_2(hass: HomeAssistant) -> None:
@@ -147,8 +150,11 @@ async def test_active_zone_skips_passive_zones_2(hass: HomeAssistant) -> None:
},
)
await hass.async_block_till_done()
active = zone.async_active_zone(hass, 32.880700, -117.237561)
assert active.entity_id == "zone.active_zone"
active_zone = zone.async_active_zone(hass, 32.880700, -117.237561)
assert active_zone.entity_id == "zone.active_zone"
active_zone, in_zones = zone.async_in_zones(hass, 32.880600, -117.237561)
assert active_zone.entity_id == "zone.active_zone"
assert in_zones == ["zone.active_zone"]
async def test_active_zone_prefers_smaller_zone_if_same_distance(
@@ -178,8 +184,11 @@ async def test_active_zone_prefers_smaller_zone_if_same_distance(
},
)
active = zone.async_active_zone(hass, latitude, longitude)
assert active.entity_id == "zone.small_zone"
active_zone = zone.async_active_zone(hass, latitude, longitude)
assert active_zone.entity_id == "zone.small_zone"
active_zone, in_zones = zone.async_in_zones(hass, latitude, longitude)
assert active_zone.entity_id == "zone.small_zone"
assert in_zones == ["zone.small_zone", "zone.big_zone"]
async def test_active_zone_prefers_smaller_zone_if_same_distance_2(
@@ -203,8 +212,11 @@ async def test_active_zone_prefers_smaller_zone_if_same_distance_2(
},
)
active = zone.async_active_zone(hass, latitude, longitude)
assert active.entity_id == "zone.smallest_zone"
active_zone = zone.async_active_zone(hass, latitude, longitude)
assert active_zone.entity_id == "zone.smallest_zone"
active_zone, in_zones = zone.async_in_zones(hass, latitude, longitude)
assert active_zone.entity_id == "zone.smallest_zone"
assert in_zones == ["zone.smallest_zone"]
async def test_in_zone_works_for_passive_zones(hass: HomeAssistant) -> None:
@@ -263,11 +275,17 @@ async def test_async_active_zone_with_non_zero_radius(
assert home_state.attributes["latitude"] == 32.87336
assert home_state.attributes["longitude"] == -117.22743
active = zone.async_active_zone(hass, latitude, longitude, 5000)
assert active.entity_id == "zone.home"
active_zone = zone.async_active_zone(hass, latitude, longitude, 5000)
assert active_zone.entity_id == "zone.home"
active_zone, in_zones = zone.async_in_zones(hass, latitude, longitude, 5000)
assert active_zone.entity_id == "zone.home"
assert in_zones == ["zone.home", "zone.small_zone", "zone.big_zone"]
active = zone.async_active_zone(hass, latitude, longitude, 0)
assert active.entity_id == "zone.small_zone"
active_zone = zone.async_active_zone(hass, latitude, longitude, 0)
assert active_zone.entity_id == "zone.small_zone"
active_zone, in_zones = zone.async_in_zones(hass, latitude, longitude, 0)
assert active_zone.entity_id == "zone.small_zone"
assert in_zones == ["zone.small_zone", "zone.big_zone"]
async def test_core_config_update(hass: HomeAssistant) -> None:
@@ -567,6 +585,9 @@ async def test_unavailable_zone(hass: HomeAssistant) -> None:
hass.states.async_set("zone.bla", "unavailable", {"restored": True})
assert zone.async_active_zone(hass, 0.0, 0.01) is None
active_zone, in_zones = zone.async_in_zones(hass, 0.0, 0.01)
assert active_zone is None
assert in_zones == []
assert zone.in_zone(hass.states.get("zone.bla"), 0, 0) is False