Make the radius of the home zone configurable (#119385)

This commit is contained in:
Erik Montnemery 2024-06-15 13:22:01 +02:00 committed by GitHub
parent 8cf1890772
commit 7e61ec96e7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 44 additions and 5 deletions

View File

@ -61,6 +61,7 @@ class CheckConfigView(HomeAssistantView):
vol.Optional("latitude"): cv.latitude, vol.Optional("latitude"): cv.latitude,
vol.Optional("location_name"): str, vol.Optional("location_name"): str,
vol.Optional("longitude"): cv.longitude, vol.Optional("longitude"): cv.longitude,
vol.Optional("radius"): cv.positive_int,
vol.Optional("time_zone"): cv.time_zone, vol.Optional("time_zone"): cv.time_zone,
vol.Optional("update_units"): bool, vol.Optional("update_units"): bool,
vol.Optional("unit_system"): unit_system.validate_unit_system, vol.Optional("unit_system"): unit_system.validate_unit_system,

View File

@ -302,7 +302,7 @@ def _home_conf(hass: HomeAssistant) -> dict:
CONF_NAME: hass.config.location_name, CONF_NAME: hass.config.location_name,
CONF_LATITUDE: hass.config.latitude, CONF_LATITUDE: hass.config.latitude,
CONF_LONGITUDE: hass.config.longitude, CONF_LONGITUDE: hass.config.longitude,
CONF_RADIUS: DEFAULT_RADIUS, CONF_RADIUS: hass.config.radius,
CONF_ICON: ICON_HOME, CONF_ICON: ICON_HOME,
CONF_PASSIVE: False, CONF_PASSIVE: False,
} }

View File

@ -52,6 +52,7 @@ from .const import (
CONF_NAME, CONF_NAME,
CONF_PACKAGES, CONF_PACKAGES,
CONF_PLATFORM, CONF_PLATFORM,
CONF_RADIUS,
CONF_TEMPERATURE_UNIT, CONF_TEMPERATURE_UNIT,
CONF_TIME_ZONE, CONF_TIME_ZONE,
CONF_TYPE, CONF_TYPE,
@ -342,6 +343,7 @@ CORE_CONFIG_SCHEMA = vol.All(
CONF_LATITUDE: cv.latitude, CONF_LATITUDE: cv.latitude,
CONF_LONGITUDE: cv.longitude, CONF_LONGITUDE: cv.longitude,
CONF_ELEVATION: vol.Coerce(int), CONF_ELEVATION: vol.Coerce(int),
CONF_RADIUS: cv.positive_int,
vol.Remove(CONF_TEMPERATURE_UNIT): cv.temperature_unit, vol.Remove(CONF_TEMPERATURE_UNIT): cv.temperature_unit,
CONF_UNIT_SYSTEM: validate_unit_system, CONF_UNIT_SYSTEM: validate_unit_system,
CONF_TIME_ZONE: cv.time_zone, CONF_TIME_ZONE: cv.time_zone,
@ -882,6 +884,7 @@ async def async_process_ha_core_config(hass: HomeAssistant, config: dict) -> Non
CONF_CURRENCY, CONF_CURRENCY,
CONF_COUNTRY, CONF_COUNTRY,
CONF_LANGUAGE, CONF_LANGUAGE,
CONF_RADIUS,
) )
): ):
hac.config_source = ConfigSource.YAML hac.config_source = ConfigSource.YAML
@ -898,6 +901,7 @@ async def async_process_ha_core_config(hass: HomeAssistant, config: dict) -> Non
(CONF_CURRENCY, "currency"), (CONF_CURRENCY, "currency"),
(CONF_COUNTRY, "country"), (CONF_COUNTRY, "country"),
(CONF_LANGUAGE, "language"), (CONF_LANGUAGE, "language"),
(CONF_RADIUS, "radius"),
): ):
if key in config: if key in config:
setattr(hac, attr, config[key]) setattr(hac, attr, config[key])

View File

@ -138,7 +138,7 @@ type CALLBACK_TYPE = Callable[[], None]
CORE_STORAGE_KEY = "core.config" CORE_STORAGE_KEY = "core.config"
CORE_STORAGE_VERSION = 1 CORE_STORAGE_VERSION = 1
CORE_STORAGE_MINOR_VERSION = 3 CORE_STORAGE_MINOR_VERSION = 4
DOMAIN = "homeassistant" DOMAIN = "homeassistant"
@ -2835,6 +2835,9 @@ class Config:
def __init__(self, hass: HomeAssistant, config_dir: str) -> None: def __init__(self, hass: HomeAssistant, config_dir: str) -> None:
"""Initialize a new config object.""" """Initialize a new config object."""
# pylint: disable-next=import-outside-toplevel
from .components.zone import DEFAULT_RADIUS
self.hass = hass self.hass = hass
self.latitude: float = 0 self.latitude: float = 0
@ -2843,6 +2846,9 @@ class Config:
self.elevation: int = 0 self.elevation: int = 0
"""Elevation (always in meters regardless of the unit system).""" """Elevation (always in meters regardless of the unit system)."""
self.radius: int = DEFAULT_RADIUS
"""Radius of the Home Zone (always in meters regardless of the unit system)."""
self.debug: bool = False self.debug: bool = False
self.location_name: str = "Home" self.location_name: str = "Home"
self.time_zone: str = "UTC" self.time_zone: str = "UTC"
@ -2991,6 +2997,7 @@ class Config:
"language": self.language, "language": self.language,
"safe_mode": self.safe_mode, "safe_mode": self.safe_mode,
"debug": self.debug, "debug": self.debug,
"radius": self.radius,
} }
async def async_set_time_zone(self, time_zone_str: str) -> None: async def async_set_time_zone(self, time_zone_str: str) -> None:
@ -3039,6 +3046,7 @@ class Config:
currency: str | None = None, currency: str | None = None,
country: str | UndefinedType | None = UNDEFINED, country: str | UndefinedType | None = UNDEFINED,
language: str | None = None, language: str | None = None,
radius: int | None = None,
) -> None: ) -> None:
"""Update the configuration from a dictionary.""" """Update the configuration from a dictionary."""
self.config_source = source self.config_source = source
@ -3067,6 +3075,8 @@ class Config:
self.country = country self.country = country
if language is not None: if language is not None:
self.language = language self.language = language
if radius is not None:
self.radius = radius
async def async_update(self, **kwargs: Any) -> None: async def async_update(self, **kwargs: Any) -> None:
"""Update the configuration from a dictionary.""" """Update the configuration from a dictionary."""
@ -3115,6 +3125,7 @@ class Config:
currency=data.get("currency"), currency=data.get("currency"),
country=data.get("country"), country=data.get("country"),
language=data.get("language"), language=data.get("language"),
radius=data["radius"],
) )
async def _async_store(self) -> None: async def _async_store(self) -> None:
@ -3133,6 +3144,7 @@ class Config:
"currency": self.currency, "currency": self.currency,
"country": self.country, "country": self.country,
"language": self.language, "language": self.language,
"radius": self.radius,
} }
await self._store.async_save(data) await self._store.async_save(data)
@ -3162,6 +3174,10 @@ class Config:
old_data: dict[str, Any], old_data: dict[str, Any],
) -> dict[str, Any]: ) -> dict[str, Any]:
"""Migrate to the new version.""" """Migrate to the new version."""
# pylint: disable-next=import-outside-toplevel
from .components.zone import DEFAULT_RADIUS
data = old_data data = old_data
if old_major_version == 1 and old_minor_version < 2: if old_major_version == 1 and old_minor_version < 2:
# In 1.2, we remove support for "imperial", replaced by "us_customary" # In 1.2, we remove support for "imperial", replaced by "us_customary"
@ -3198,6 +3214,9 @@ class Config:
# pylint: disable-next=broad-except # pylint: disable-next=broad-except
except Exception: except Exception:
_LOGGER.exception("Unexpected error during core config migration") _LOGGER.exception("Unexpected error during core config migration")
if old_major_version == 1 and old_minor_version < 4:
# In 1.4, we add the key "radius", initialize it with the default.
data.setdefault("radius", DEFAULT_RADIUS)
if old_major_version > 1: if old_major_version > 1:
raise NotImplementedError raise NotImplementedError

View File

@ -120,6 +120,7 @@ async def test_websocket_core_update(hass: HomeAssistant, client) -> None:
assert hass.config.currency == "EUR" assert hass.config.currency == "EUR"
assert hass.config.country != "SE" assert hass.config.country != "SE"
assert hass.config.language != "sv" assert hass.config.language != "sv"
assert hass.config.radius != 150
with ( with (
patch("homeassistant.util.dt.set_default_time_zone") as mock_set_tz, patch("homeassistant.util.dt.set_default_time_zone") as mock_set_tz,
@ -142,6 +143,7 @@ async def test_websocket_core_update(hass: HomeAssistant, client) -> None:
"currency": "USD", "currency": "USD",
"country": "SE", "country": "SE",
"language": "sv", "language": "sv",
"radius": 150,
} }
) )
@ -162,6 +164,7 @@ async def test_websocket_core_update(hass: HomeAssistant, client) -> None:
assert hass.config.currency == "USD" assert hass.config.currency == "USD"
assert hass.config.country == "SE" assert hass.config.country == "SE"
assert hass.config.language == "sv" assert hass.config.language == "sv"
assert hass.config.radius == 150
assert len(mock_set_tz.mock_calls) == 1 assert len(mock_set_tz.mock_calls) == 1
assert mock_set_tz.mock_calls[0][1][0] == dt_util.get_time_zone("America/New_York") assert mock_set_tz.mock_calls[0][1][0] == dt_util.get_time_zone("America/New_York")

View File

@ -522,6 +522,7 @@ def test_core_config_schema() -> None:
{"customize": {"entity_id": []}}, {"customize": {"entity_id": []}},
{"country": "xx"}, {"country": "xx"},
{"language": "xx"}, {"language": "xx"},
{"radius": -10},
): ):
with pytest.raises(MultipleInvalid): with pytest.raises(MultipleInvalid):
config_util.CORE_CONFIG_SCHEMA(value) config_util.CORE_CONFIG_SCHEMA(value)
@ -538,6 +539,7 @@ def test_core_config_schema() -> None:
"customize": {"sensor.temperature": {"hidden": True}}, "customize": {"sensor.temperature": {"hidden": True}},
"country": "SE", "country": "SE",
"language": "sv", "language": "sv",
"radius": "10",
} }
) )
@ -709,10 +711,11 @@ async def test_loading_configuration_from_storage(
"currency": "EUR", "currency": "EUR",
"country": "SE", "country": "SE",
"language": "sv", "language": "sv",
"radius": 150,
}, },
"key": "core.config", "key": "core.config",
"version": 1, "version": 1,
"minor_version": 3, "minor_version": 4,
} }
await config_util.async_process_ha_core_config( await config_util.async_process_ha_core_config(
hass, {"allowlist_external_dirs": "/etc"} hass, {"allowlist_external_dirs": "/etc"}
@ -729,6 +732,7 @@ async def test_loading_configuration_from_storage(
assert hass.config.currency == "EUR" assert hass.config.currency == "EUR"
assert hass.config.country == "SE" assert hass.config.country == "SE"
assert hass.config.language == "sv" assert hass.config.language == "sv"
assert hass.config.radius == 150
assert len(hass.config.allowlist_external_dirs) == 3 assert len(hass.config.allowlist_external_dirs) == 3
assert "/etc" in hass.config.allowlist_external_dirs assert "/etc" in hass.config.allowlist_external_dirs
assert hass.config.config_source is ConfigSource.STORAGE assert hass.config.config_source is ConfigSource.STORAGE
@ -798,15 +802,19 @@ async def test_migration_and_updating_configuration(
expected_new_core_data["data"]["currency"] = "USD" expected_new_core_data["data"]["currency"] = "USD"
# 1.1 -> 1.2 store migration with migrated unit system # 1.1 -> 1.2 store migration with migrated unit system
expected_new_core_data["data"]["unit_system_v2"] = "us_customary" expected_new_core_data["data"]["unit_system_v2"] = "us_customary"
expected_new_core_data["minor_version"] = 3 # 1.1 -> 1.3 defaults for country and language
# defaults for country and language
expected_new_core_data["data"]["country"] = None expected_new_core_data["data"]["country"] = None
expected_new_core_data["data"]["language"] = "en" expected_new_core_data["data"]["language"] = "en"
# 1.1 -> 1.4 defaults for zone radius
expected_new_core_data["data"]["radius"] = 100
# Bumped minor version
expected_new_core_data["minor_version"] = 4
assert hass_storage["core.config"] == expected_new_core_data assert hass_storage["core.config"] == expected_new_core_data
assert hass.config.latitude == 50 assert hass.config.latitude == 50
assert hass.config.currency == "USD" assert hass.config.currency == "USD"
assert hass.config.country is None assert hass.config.country is None
assert hass.config.language == "en" assert hass.config.language == "en"
assert hass.config.radius == 100
async def test_override_stored_configuration( async def test_override_stored_configuration(
@ -860,6 +868,7 @@ async def test_loading_configuration(hass: HomeAssistant) -> None:
"currency": "EUR", "currency": "EUR",
"country": "SE", "country": "SE",
"language": "sv", "language": "sv",
"radius": 150,
}, },
) )
@ -881,6 +890,7 @@ async def test_loading_configuration(hass: HomeAssistant) -> None:
assert hass.config.currency == "EUR" assert hass.config.currency == "EUR"
assert hass.config.country == "SE" assert hass.config.country == "SE"
assert hass.config.language == "sv" assert hass.config.language == "sv"
assert hass.config.radius == 150
@pytest.mark.parametrize( @pytest.mark.parametrize(

View File

@ -1936,6 +1936,7 @@ async def test_config_defaults() -> None:
assert config.currency == "EUR" assert config.currency == "EUR"
assert config.country is None assert config.country is None
assert config.language == "en" assert config.language == "en"
assert config.radius == 100
async def test_config_path_with_file() -> None: async def test_config_path_with_file() -> None:
@ -1983,6 +1984,7 @@ async def test_config_as_dict() -> None:
"language": "en", "language": "en",
"safe_mode": False, "safe_mode": False,
"debug": False, "debug": False,
"radius": 100,
} }
assert expected == config.as_dict() assert expected == config.as_dict()