diff --git a/homeassistant/components/proximity/__init__.py b/homeassistant/components/proximity/__init__.py index 4012d6e8ea1..c4ab915b577 100644 --- a/homeassistant/components/proximity/__init__.py +++ b/homeassistant/components/proximity/__init__.py @@ -5,15 +5,22 @@ import logging import voluptuous as vol -from homeassistant.const import CONF_DEVICES, CONF_UNIT_OF_MEASUREMENT, CONF_ZONE +from homeassistant.const import ( + CONF_DEVICES, + CONF_NAME, + CONF_UNIT_OF_MEASUREMENT, + CONF_ZONE, +) from homeassistant.core import HomeAssistant import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.discovery import async_load_platform from homeassistant.helpers.event import async_track_state_change from homeassistant.helpers.typing import ConfigType from homeassistant.helpers.update_coordinator import CoordinatorEntity from .const import ( ATTR_DIR_OF_TRAVEL, + ATTR_DIST_TO, ATTR_NEAREST, CONF_IGNORED_ZONES, CONF_TOLERANCE, @@ -46,10 +53,12 @@ CONFIG_SCHEMA = vol.Schema( async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Get the zones and offsets from configuration.yaml.""" hass.data.setdefault(DOMAIN, {}) - for zone, proximity_config in config[DOMAIN].items(): - _LOGGER.debug("setup %s with config:%s", zone, proximity_config) + for friendly_name, proximity_config in config[DOMAIN].items(): + _LOGGER.debug("setup %s with config:%s", friendly_name, proximity_config) - coordinator = ProximityDataUpdateCoordinator(hass, zone, proximity_config) + coordinator = ProximityDataUpdateCoordinator( + hass, friendly_name, proximity_config + ) async_track_state_change( hass, @@ -58,12 +67,19 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: ) await coordinator.async_refresh() - hass.data[DOMAIN][zone] = coordinator + hass.data[DOMAIN][friendly_name] = coordinator - proximity = Proximity(hass, zone, coordinator) + proximity = Proximity(hass, friendly_name, coordinator) await proximity.async_added_to_hass() proximity.async_write_ha_state() + await async_load_platform( + hass, + "sensor", + DOMAIN, + {CONF_NAME: friendly_name, **proximity_config}, + config, + ) return True @@ -91,12 +107,14 @@ class Proximity(CoordinatorEntity[ProximityDataUpdateCoordinator]): @property def state(self) -> str | int | float: """Return the state.""" - return self.coordinator.data["dist_to_zone"] + return self.coordinator.data.proximity[ATTR_DIST_TO] @property def extra_state_attributes(self) -> dict[str, str]: """Return the state attributes.""" return { - ATTR_DIR_OF_TRAVEL: str(self.coordinator.data["dir_of_travel"]), - ATTR_NEAREST: str(self.coordinator.data["nearest"]), + ATTR_DIR_OF_TRAVEL: str( + self.coordinator.data.proximity[ATTR_DIR_OF_TRAVEL] + ), + ATTR_NEAREST: str(self.coordinator.data.proximity[ATTR_NEAREST]), } diff --git a/homeassistant/components/proximity/const.py b/homeassistant/components/proximity/const.py index a5cee0ffce3..166029fef37 100644 --- a/homeassistant/components/proximity/const.py +++ b/homeassistant/components/proximity/const.py @@ -1,10 +1,15 @@ """Constants for Proximity integration.""" +from typing import Final + from homeassistant.const import UnitOfLength -ATTR_DIR_OF_TRAVEL = "dir_of_travel" -ATTR_DIST_TO = "dist_to_zone" -ATTR_NEAREST = "nearest" +ATTR_DIR_OF_TRAVEL: Final = "dir_of_travel" +ATTR_DIST_TO: Final = "dist_to_zone" +ATTR_ENTITIES_DATA: Final = "entities_data" +ATTR_IN_IGNORED_ZONE: Final = "is_in_ignored_zone" +ATTR_NEAREST: Final = "nearest" +ATTR_PROXIMITY_DATA: Final = "proximity_data" CONF_IGNORED_ZONES = "ignored_zones" CONF_TOLERANCE = "tolerance" diff --git a/homeassistant/components/proximity/coordinator.py b/homeassistant/components/proximity/coordinator.py index 1f1c96c9490..05561bd0406 100644 --- a/homeassistant/components/proximity/coordinator.py +++ b/homeassistant/components/proximity/coordinator.py @@ -2,11 +2,11 @@ from dataclasses import dataclass import logging -from typing import TypedDict from homeassistant.const import ( ATTR_LATITUDE, ATTR_LONGITUDE, + ATTR_NAME, CONF_DEVICES, CONF_UNIT_OF_MEASUREMENT, CONF_ZONE, @@ -19,6 +19,10 @@ from homeassistant.util.location import distance from homeassistant.util.unit_conversion import DistanceConverter from .const import ( + ATTR_DIR_OF_TRAVEL, + ATTR_DIST_TO, + ATTR_IN_IGNORED_ZONE, + ATTR_NEAREST, CONF_IGNORED_ZONES, CONF_TOLERANCE, DEFAULT_DIR_OF_TRAVEL, @@ -38,12 +42,22 @@ class StateChangedData: new_state: State | None -class ProximityData(TypedDict): - """ProximityData type class.""" +@dataclass +class ProximityData: + """ProximityCoordinatorData class.""" - dist_to_zone: str | float - dir_of_travel: str | float - nearest: str | float + proximity: dict[str, str | float] + entities: dict[str, dict[str, str | int | None]] + + +DEFAULT_DATA = ProximityData( + { + ATTR_DIST_TO: DEFAULT_DIST_TO_ZONE, + ATTR_DIR_OF_TRAVEL: DEFAULT_DIR_OF_TRAVEL, + ATTR_NEAREST: DEFAULT_NEAREST, + }, + {}, +) class ProximityDataUpdateCoordinator(DataUpdateCoordinator[ProximityData]): @@ -54,7 +68,7 @@ class ProximityDataUpdateCoordinator(DataUpdateCoordinator[ProximityData]): ) -> None: """Initialize the Proximity coordinator.""" self.ignored_zones: list[str] = config[CONF_IGNORED_ZONES] - self.proximity_devices: list[str] = config[CONF_DEVICES] + self.tracked_entities: list[str] = config[CONF_DEVICES] self.tolerance: int = config[CONF_TOLERANCE] self.proximity_zone: str = config[CONF_ZONE] self.unit_of_measurement: str = config.get( @@ -69,11 +83,7 @@ class ProximityDataUpdateCoordinator(DataUpdateCoordinator[ProximityData]): update_interval=None, ) - self.data = { - "dist_to_zone": DEFAULT_DIST_TO_ZONE, - "dir_of_travel": DEFAULT_DIR_OF_TRAVEL, - "nearest": DEFAULT_NEAREST, - } + self.data = DEFAULT_DATA self.state_change_data: StateChangedData | None = None @@ -81,177 +91,216 @@ class ProximityDataUpdateCoordinator(DataUpdateCoordinator[ProximityData]): self, entity: str, old_state: State | None, new_state: State | None ) -> None: """Fetch and process state change event.""" - if new_state is None: - _LOGGER.debug("no new_state -> abort") - return - self.state_change_data = StateChangedData(entity, old_state, new_state) await self.async_refresh() - async def _async_update_data(self) -> ProximityData: - """Calculate Proximity data.""" - if ( - state_change_data := self.state_change_data - ) is None or state_change_data.new_state is None: - return self.data - - entity_name = state_change_data.new_state.name - devices_to_calculate = False - devices_in_zone = [] - - zone_state = self.hass.states.get(f"zone.{self.proximity_zone}") - proximity_latitude = ( - zone_state.attributes.get(ATTR_LATITUDE) if zone_state else None - ) - proximity_longitude = ( - zone_state.attributes.get(ATTR_LONGITUDE) if zone_state else None + def _convert(self, value: float | str) -> float | str: + """Round and convert given distance value.""" + if isinstance(value, str): + return value + return round( + DistanceConverter.convert( + value, + UnitOfLength.METERS, + self.unit_of_measurement, + ) ) - # Check for devices in the monitored zone. - for device in self.proximity_devices: - if (device_state := self.hass.states.get(device)) is None: - devices_to_calculate = True - continue - - if device_state.state not in self.ignored_zones: - devices_to_calculate = True - - # Check the location of all devices. - if (device_state.state).lower() == (self.proximity_zone).lower(): - device_friendly = device_state.name - devices_in_zone.append(device_friendly) - - # No-one to track so reset the entity. - if not devices_to_calculate: - _LOGGER.debug("no devices_to_calculate -> abort") - return { - "dist_to_zone": DEFAULT_DIST_TO_ZONE, - "dir_of_travel": DEFAULT_DIR_OF_TRAVEL, - "nearest": DEFAULT_NEAREST, - } - - # At least one device is in the monitored zone so update the entity. - if devices_in_zone: - _LOGGER.debug("at least one device is in zone -> arrived") - return { - "dist_to_zone": 0, - "dir_of_travel": "arrived", - "nearest": ", ".join(devices_in_zone), - } - - # We can't check proximity because latitude and longitude don't exist. - if "latitude" not in state_change_data.new_state.attributes: - _LOGGER.debug("no latitude and longitude -> reset") - return self.data - - # Collect distances to the zone for all devices. - distances_to_zone: dict[str, float] = {} - for device in self.proximity_devices: - # Ignore devices in an ignored zone. - device_state = self.hass.states.get(device) - if not device_state or device_state.state in self.ignored_zones: - continue - - # Ignore devices if proximity cannot be calculated. - if "latitude" not in device_state.attributes: - continue - - # Calculate the distance to the proximity zone. - proximity = distance( - proximity_latitude, - proximity_longitude, - device_state.attributes[ATTR_LATITUDE], - device_state.attributes[ATTR_LONGITUDE], + def _calc_distance_to_zone( + self, + zone: State, + device: State, + latitude: float | None, + longitude: float | None, + ) -> int | None: + if device.state.lower() == self.proximity_zone.lower(): + _LOGGER.debug( + "%s: %s in zone -> distance=0", + self.friendly_name, + device.entity_id, ) + return 0 - # Add the device and distance to a dictionary. - if proximity is None: - continue - distances_to_zone[device] = round( - DistanceConverter.convert( - proximity, UnitOfLength.METERS, self.unit_of_measurement - ), - 1, + if latitude is None or longitude is None: + _LOGGER.debug( + "%s: %s has no coordinates -> distance=None", + self.friendly_name, + device.entity_id, ) + return None - # Loop through each of the distances collected and work out the - # closest. - closest_device: str | None = None - dist_to_zone: float | None = None + distance_to_zone = distance( + zone.attributes[ATTR_LATITUDE], + zone.attributes[ATTR_LONGITUDE], + latitude, + longitude, + ) - for device, zone in distances_to_zone.items(): - if not dist_to_zone or zone < dist_to_zone: - closest_device = device - dist_to_zone = zone + # it is ensured, that distance can't be None, since zones must have lat/lon coordinates + assert distance_to_zone is not None + return round(distance_to_zone) - # If the closest device is one of the other devices. - if closest_device is not None and closest_device != state_change_data.entity_id: - _LOGGER.debug("closest device is one of the other devices -> unknown") - device_state = self.hass.states.get(closest_device) - assert device_state - return { - "dist_to_zone": round(distances_to_zone[closest_device]), - "dir_of_travel": "unknown", - "nearest": device_state.name, - } + def _calc_direction_of_travel( + self, + zone: State, + device: State, + old_latitude: float | None, + old_longitude: float | None, + new_latitude: float | None, + new_longitude: float | None, + ) -> str | None: + if device.state.lower() == self.proximity_zone.lower(): + _LOGGER.debug( + "%s: %s in zone -> direction_of_travel=arrived", + self.friendly_name, + device.entity_id, + ) + return "arrived" - # Stop if we cannot calculate the direction of travel (i.e. we don't - # have a previous state and a current LAT and LONG). if ( - state_change_data.old_state is None - or "latitude" not in state_change_data.old_state.attributes + old_latitude is None + or old_longitude is None + or new_latitude is None + or new_longitude is None ): - _LOGGER.debug("no lat and lon in old_state -> unknown") - return { - "dist_to_zone": round(distances_to_zone[state_change_data.entity_id]), - "dir_of_travel": "unknown", - "nearest": entity_name, - } + return None - # Reset the variables - distance_travelled: float = 0 - - # Calculate the distance travelled. old_distance = distance( - proximity_latitude, - proximity_longitude, - state_change_data.old_state.attributes[ATTR_LATITUDE], - state_change_data.old_state.attributes[ATTR_LONGITUDE], + zone.attributes[ATTR_LATITUDE], + zone.attributes[ATTR_LONGITUDE], + old_latitude, + old_longitude, ) new_distance = distance( - proximity_latitude, - proximity_longitude, - state_change_data.new_state.attributes[ATTR_LATITUDE], - state_change_data.new_state.attributes[ATTR_LONGITUDE], + zone.attributes[ATTR_LATITUDE], + zone.attributes[ATTR_LONGITUDE], + new_latitude, + new_longitude, ) - assert new_distance is not None and old_distance is not None + + # it is ensured, that distance can't be None, since zones must have lat/lon coordinates + assert old_distance is not None + assert new_distance is not None distance_travelled = round(new_distance - old_distance, 1) - # Check for tolerance if distance_travelled < self.tolerance * -1: - direction_of_travel = "towards" - elif distance_travelled > self.tolerance: - direction_of_travel = "away_from" - else: - direction_of_travel = "stationary" + return "towards" - # Update the proximity entity - dist_to: float | str - if dist_to_zone is not None: - dist_to = round(dist_to_zone) - else: - dist_to = DEFAULT_DIST_TO_ZONE + if distance_travelled > self.tolerance: + return "away_from" - _LOGGER.debug( - "%s updated: distance=%s: direction=%s: device=%s", - self.friendly_name, - dist_to, - direction_of_travel, - entity_name, - ) + return "stationary" - return { - "dist_to_zone": dist_to, - "dir_of_travel": direction_of_travel, - "nearest": entity_name, + async def _async_update_data(self) -> ProximityData: + """Calculate Proximity data.""" + if (zone_state := self.hass.states.get(f"zone.{self.proximity_zone}")) is None: + _LOGGER.debug( + "%s: zone %s does not exist -> reset", + self.friendly_name, + self.proximity_zone, + ) + return DEFAULT_DATA + + entities_data = self.data.entities + + # calculate distance for all tracked entities + for entity_id in self.tracked_entities: + if (tracked_entity_state := self.hass.states.get(entity_id)) is None: + if entities_data.pop(entity_id, None) is not None: + _LOGGER.debug( + "%s: %s does not exist -> remove", self.friendly_name, entity_id + ) + continue + + if entity_id not in entities_data: + _LOGGER.debug("%s: %s is new -> add", self.friendly_name, entity_id) + entities_data[entity_id] = { + ATTR_DIST_TO: None, + ATTR_DIR_OF_TRAVEL: None, + ATTR_NAME: tracked_entity_state.name, + ATTR_IN_IGNORED_ZONE: False, + } + entities_data[entity_id][ATTR_IN_IGNORED_ZONE] = ( + tracked_entity_state.state.lower() in self.ignored_zones + ) + entities_data[entity_id][ATTR_DIST_TO] = self._calc_distance_to_zone( + zone_state, + tracked_entity_state, + tracked_entity_state.attributes.get(ATTR_LATITUDE), + tracked_entity_state.attributes.get(ATTR_LONGITUDE), + ) + if entities_data[entity_id][ATTR_DIST_TO] is None: + _LOGGER.debug( + "%s: %s has unknown distance got -> direction_of_travel=None", + self.friendly_name, + entity_id, + ) + entities_data[entity_id][ATTR_DIR_OF_TRAVEL] = None + + # calculate direction of travel only for last updated tracked entity + if (state_change_data := self.state_change_data) is not None and ( + new_state := state_change_data.new_state + ) is not None: + _LOGGER.debug( + "%s: calculate direction of travel for %s", + self.friendly_name, + state_change_data.entity_id, + ) + + if (old_state := state_change_data.old_state) is not None: + old_lat = old_state.attributes.get(ATTR_LATITUDE) + old_lon = old_state.attributes.get(ATTR_LONGITUDE) + else: + old_lat = None + old_lon = None + + entities_data[state_change_data.entity_id][ + ATTR_DIR_OF_TRAVEL + ] = self._calc_direction_of_travel( + zone_state, + new_state, + old_lat, + old_lon, + new_state.attributes.get(ATTR_LATITUDE), + new_state.attributes.get(ATTR_LONGITUDE), + ) + + # takeover data for legacy proximity entity + proximity_data: dict[str, str | float] = { + ATTR_DIST_TO: DEFAULT_DIST_TO_ZONE, + ATTR_DIR_OF_TRAVEL: DEFAULT_DIR_OF_TRAVEL, + ATTR_NEAREST: DEFAULT_NEAREST, } + for entity_data in entities_data.values(): + if (distance_to := entity_data[ATTR_DIST_TO]) is None or entity_data[ + ATTR_IN_IGNORED_ZONE + ]: + continue + + if isinstance((nearest_distance_to := proximity_data[ATTR_DIST_TO]), str): + _LOGGER.debug("set first entity_data: %s", entity_data) + proximity_data = { + ATTR_DIST_TO: distance_to, + ATTR_DIR_OF_TRAVEL: entity_data[ATTR_DIR_OF_TRAVEL] or "unknown", + ATTR_NEAREST: str(entity_data[ATTR_NAME]), + } + continue + + if float(nearest_distance_to) > float(distance_to): + _LOGGER.debug("set closer entity_data: %s", entity_data) + proximity_data = { + ATTR_DIST_TO: distance_to, + ATTR_DIR_OF_TRAVEL: entity_data[ATTR_DIR_OF_TRAVEL] or "unknown", + ATTR_NEAREST: str(entity_data[ATTR_NAME]), + } + continue + + if float(nearest_distance_to) == float(distance_to): + _LOGGER.debug("set equally close entity_data: %s", entity_data) + proximity_data[ + ATTR_NEAREST + ] = f"{proximity_data[ATTR_NEAREST]}, {str(entity_data[ATTR_NAME])}" + + proximity_data[ATTR_DIST_TO] = self._convert(proximity_data[ATTR_DIST_TO]) + + return ProximityData(proximity_data, entities_data) diff --git a/homeassistant/components/proximity/sensor.py b/homeassistant/components/proximity/sensor.py new file mode 100644 index 00000000000..44121dcacb4 --- /dev/null +++ b/homeassistant/components/proximity/sensor.py @@ -0,0 +1,138 @@ +"""Support for Proximity sensors.""" + +from __future__ import annotations + +from homeassistant.components.sensor import ( + SensorDeviceClass, + SensorEntity, + SensorEntityDescription, +) +from homeassistant.const import CONF_NAME, UnitOfLength +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType +from homeassistant.helpers.update_coordinator import CoordinatorEntity + +from .const import ATTR_DIR_OF_TRAVEL, ATTR_DIST_TO, ATTR_NEAREST, DOMAIN +from .coordinator import ProximityDataUpdateCoordinator + +SENSORS_PER_ENTITY: list[SensorEntityDescription] = [ + SensorEntityDescription( + key=ATTR_DIST_TO, + name="Distance", + device_class=SensorDeviceClass.DISTANCE, + native_unit_of_measurement=UnitOfLength.METERS, + ), + SensorEntityDescription( + key=ATTR_DIR_OF_TRAVEL, + name="Direction of travel", + translation_key=ATTR_DIR_OF_TRAVEL, + icon="mdi:compass-outline", + device_class=SensorDeviceClass.ENUM, + options=[ + "arrived", + "away_from", + "stationary", + "towards", + ], + ), +] + +SENSORS_PER_PROXIMITY: list[SensorEntityDescription] = [ + SensorEntityDescription( + key=ATTR_NEAREST, + name="Nearest", + translation_key=ATTR_NEAREST, + icon="mdi:near-me", + ), +] + + +async def async_setup_platform( + hass: HomeAssistant, + config: ConfigType, + async_add_entities: AddEntitiesCallback, + discovery_info: DiscoveryInfoType | None = None, +) -> None: + """Set up the Proximity sensor platform.""" + if discovery_info is None: + return + + coordinator: ProximityDataUpdateCoordinator = hass.data[DOMAIN][ + discovery_info[CONF_NAME] + ] + + entities: list[ProximitySensor | ProximityTrackedEntitySensor] = [ + ProximitySensor(description, coordinator) + for description in SENSORS_PER_PROXIMITY + ] + + entities += [ + ProximityTrackedEntitySensor(description, coordinator, tracked_entity_id) + for description in SENSORS_PER_ENTITY + for tracked_entity_id in coordinator.tracked_entities + ] + + async_add_entities(entities) + + +class ProximitySensor(CoordinatorEntity[ProximityDataUpdateCoordinator], SensorEntity): + """Represents a Proximity sensor.""" + + _attr_has_entity_name = True + + def __init__( + self, + description: SensorEntityDescription, + coordinator: ProximityDataUpdateCoordinator, + ) -> None: + """Initialize the proximity.""" + super().__init__(coordinator) + + self.entity_description = description + + # entity name will be removed as soon as we have a config entry + # and can follow the entity naming guidelines + self._attr_name = f"{coordinator.friendly_name} {description.name}" + + @property + def native_value(self) -> str | float | None: + """Return native sensor value.""" + if ( + value := self.coordinator.data.proximity[self.entity_description.key] + ) == "not set": + return None + return value + + +class ProximityTrackedEntitySensor( + CoordinatorEntity[ProximityDataUpdateCoordinator], SensorEntity +): + """Represents a Proximity tracked entity sensor.""" + + _attr_has_entity_name = True + + def __init__( + self, + description: SensorEntityDescription, + coordinator: ProximityDataUpdateCoordinator, + tracked_entity_id: str, + ) -> None: + """Initialize the proximity.""" + super().__init__(coordinator) + + self.entity_description = description + self.tracked_entity_id = tracked_entity_id + + # entity name will be removed as soon as we have a config entry + # and can follow the entity naming guidelines + self._attr_name = ( + f"{coordinator.friendly_name} {tracked_entity_id} {description.name}" + ) + + @property + def native_value(self) -> str | float | None: + """Return native sensor value.""" + if (data := self.coordinator.data.entities.get(self.tracked_entity_id)) is None: + return None + return data.get(self.entity_description.key) diff --git a/homeassistant/components/proximity/strings.json b/homeassistant/components/proximity/strings.json index 4949ec80ba1..56802e08051 100644 --- a/homeassistant/components/proximity/strings.json +++ b/homeassistant/components/proximity/strings.json @@ -1,3 +1,17 @@ { - "title": "Proximity" + "title": "Proximity", + "entity": { + "sensor": { + "dir_of_travel": { + "name": "Direction of travel", + "state": { + "arrived": "Arrived", + "away_from": "Away from", + "stationary": "Stationary", + "towards": "Towards" + } + }, + "nearest": { "name": "Nearest device" } + } + } } diff --git a/tests/components/proximity/test_init.py b/tests/components/proximity/test_init.py index cd96d0d7b81..5a3fee629ac 100644 --- a/tests/components/proximity/test_init.py +++ b/tests/components/proximity/test_init.py @@ -1,10 +1,16 @@ """The tests for the Proximity component.""" + +import pytest + from homeassistant.components.proximity import DOMAIN +from homeassistant.const import STATE_UNKNOWN from homeassistant.core import HomeAssistant from homeassistant.setup import async_setup_component +from homeassistant.util import slugify -async def test_proximities(hass: HomeAssistant) -> None: +@pytest.mark.parametrize(("friendly_name"), ["home", "home_test2", "work"]) +async def test_proximities(hass: HomeAssistant, friendly_name: str) -> None: """Test a list of proximities.""" config = { "proximity": { @@ -27,19 +33,28 @@ async def test_proximities(hass: HomeAssistant) -> None: } assert await async_setup_component(hass, DOMAIN, config) + await hass.async_block_till_done() - proximities = ["home", "home_test2", "work"] + # proximity entity + state = hass.states.get(f"proximity.{friendly_name}") + assert state.state == "not set" + assert state.attributes.get("nearest") == "not set" + assert state.attributes.get("dir_of_travel") == "not set" + hass.states.async_set(f"proximity.{friendly_name}", "0") + await hass.async_block_till_done() + state = hass.states.get(f"proximity.{friendly_name}") + assert state.state == "0" - for prox in proximities: - state = hass.states.get(f"proximity.{prox}") - assert state.state == "not set" - assert state.attributes.get("nearest") == "not set" - assert state.attributes.get("dir_of_travel") == "not set" + # sensor entities + state = hass.states.get(f"sensor.{friendly_name}_nearest") + assert state.state == STATE_UNKNOWN - hass.states.async_set(f"proximity.{prox}", "0") - await hass.async_block_till_done() - state = hass.states.get(f"proximity.{prox}") - assert state.state == "0" + for device in config["proximity"][friendly_name]["devices"]: + entity_base_name = f"sensor.{friendly_name}_{slugify(device)}" + state = hass.states.get(f"{entity_base_name}_distance") + assert state.state == STATE_UNKNOWN + state = hass.states.get(f"{entity_base_name}_direction_of_travel") + assert state.state == STATE_UNKNOWN async def test_proximities_setup(hass: HomeAssistant) -> None: @@ -58,31 +73,6 @@ async def test_proximities_setup(hass: HomeAssistant) -> None: assert await async_setup_component(hass, DOMAIN, config) -async def test_proximity(hass: HomeAssistant) -> None: - """Test the proximity.""" - config = { - "proximity": { - "home": { - "ignored_zones": ["work"], - "devices": ["device_tracker.test1", "device_tracker.test2"], - "tolerance": "1", - } - } - } - - assert await async_setup_component(hass, DOMAIN, config) - - state = hass.states.get("proximity.home") - assert state.state == "not set" - assert state.attributes.get("nearest") == "not set" - assert state.attributes.get("dir_of_travel") == "not set" - - hass.states.async_set("proximity.home", "0") - await hass.async_block_till_done() - state = hass.states.get("proximity.home") - assert state.state == "0" - - async def test_device_tracker_test1_in_zone(hass: HomeAssistant) -> None: """Test for tracker in zone.""" config = { @@ -103,11 +93,317 @@ async def test_device_tracker_test1_in_zone(hass: HomeAssistant) -> None: {"friendly_name": "test1", "latitude": 2.1, "longitude": 1.1}, ) await hass.async_block_till_done() + + # proximity entity state = hass.states.get("proximity.home") assert state.state == "0" assert state.attributes.get("nearest") == "test1" assert state.attributes.get("dir_of_travel") == "arrived" + # sensor entities + state = hass.states.get("sensor.home_nearest") + assert state.state == "test1" + + entity_base_name = f"sensor.home_{slugify('device_tracker.test1')}" + state = hass.states.get(f"{entity_base_name}_distance") + assert state.state == "0" + state = hass.states.get(f"{entity_base_name}_direction_of_travel") + assert state.state == "arrived" + + +async def test_device_tracker_test1_away(hass: HomeAssistant) -> None: + """Test for tracker state away.""" + config = { + "proximity": { + "home": { + "ignored_zones": ["work"], + "devices": ["device_tracker.test1"], + "tolerance": "1", + } + } + } + + assert await async_setup_component(hass, DOMAIN, config) + + hass.states.async_set( + "device_tracker.test1", + "not_home", + {"friendly_name": "test1", "latitude": 20.1, "longitude": 10.1}, + ) + + await hass.async_block_till_done() + + # proximity entity + state = hass.states.get("proximity.home") + assert state.attributes.get("nearest") == "test1" + assert state.attributes.get("dir_of_travel") == "unknown" + + # sensor entities + state = hass.states.get("sensor.home_nearest") + assert state.state == "test1" + + entity_base_name = f"sensor.home_{slugify('device_tracker.test1')}" + state = hass.states.get(f"{entity_base_name}_distance") + assert state.state == "11912010" + state = hass.states.get(f"{entity_base_name}_direction_of_travel") + assert state.state == STATE_UNKNOWN + + +async def test_device_tracker_test1_awayfurther(hass: HomeAssistant) -> None: + """Test for tracker state away further.""" + + config_zones(hass) + await hass.async_block_till_done() + + config = { + "proximity": { + "home": { + "ignored_zones": ["work"], + "devices": ["device_tracker.test1"], + "tolerance": "1", + } + } + } + + assert await async_setup_component(hass, DOMAIN, config) + + hass.states.async_set( + "device_tracker.test1", + "not_home", + {"friendly_name": "test1", "latitude": 20.1, "longitude": 10.1}, + ) + await hass.async_block_till_done() + + # proximity entity + state = hass.states.get("proximity.home") + assert state.attributes.get("nearest") == "test1" + assert state.attributes.get("dir_of_travel") == "unknown" + + # sensor entities + state = hass.states.get("sensor.home_nearest") + assert state.state == "test1" + + entity_base_name = f"sensor.home_{slugify('device_tracker.test1')}" + state = hass.states.get(f"{entity_base_name}_distance") + assert state.state == "2218752" + state = hass.states.get(f"{entity_base_name}_direction_of_travel") + assert state.state == STATE_UNKNOWN + + hass.states.async_set( + "device_tracker.test1", + "not_home", + {"friendly_name": "test1", "latitude": 40.1, "longitude": 20.1}, + ) + await hass.async_block_till_done() + + # proximity entity + state = hass.states.get("proximity.home") + assert state.attributes.get("nearest") == "test1" + assert state.attributes.get("dir_of_travel") == "away_from" + + # sensor entities + state = hass.states.get("sensor.home_nearest") + assert state.state == "test1" + + entity_base_name = f"sensor.home_{slugify('device_tracker.test1')}" + state = hass.states.get(f"{entity_base_name}_distance") + assert state.state == "4625264" + state = hass.states.get(f"{entity_base_name}_direction_of_travel") + assert state.state == "away_from" + + +async def test_device_tracker_test1_awaycloser(hass: HomeAssistant) -> None: + """Test for tracker state away closer.""" + config_zones(hass) + await hass.async_block_till_done() + + config = { + "proximity": { + "home": { + "ignored_zones": ["work"], + "devices": ["device_tracker.test1"], + "tolerance": "1", + } + } + } + + assert await async_setup_component(hass, DOMAIN, config) + + hass.states.async_set( + "device_tracker.test1", + "not_home", + {"friendly_name": "test1", "latitude": 40.1, "longitude": 20.1}, + ) + await hass.async_block_till_done() + + # proximity entity + state = hass.states.get("proximity.home") + assert state.attributes.get("nearest") == "test1" + assert state.attributes.get("dir_of_travel") == "unknown" + + # sensor entities + state = hass.states.get("sensor.home_nearest") + assert state.state == "test1" + + entity_base_name = f"sensor.home_{slugify('device_tracker.test1')}" + state = hass.states.get(f"{entity_base_name}_distance") + assert state.state == "4625264" + state = hass.states.get(f"{entity_base_name}_direction_of_travel") + assert state.state == STATE_UNKNOWN + + hass.states.async_set( + "device_tracker.test1", + "not_home", + {"friendly_name": "test1", "latitude": 20.1, "longitude": 10.1}, + ) + await hass.async_block_till_done() + + # proximity entity + state = hass.states.get("proximity.home") + assert state.attributes.get("nearest") == "test1" + assert state.attributes.get("dir_of_travel") == "towards" + + # sensor entities + state = hass.states.get("sensor.home_nearest") + assert state.state == "test1" + + entity_base_name = f"sensor.home_{slugify('device_tracker.test1')}" + state = hass.states.get(f"{entity_base_name}_distance") + assert state.state == "2218752" + state = hass.states.get(f"{entity_base_name}_direction_of_travel") + assert state.state == "towards" + + +async def test_all_device_trackers_in_ignored_zone(hass: HomeAssistant) -> None: + """Test for tracker in ignored zone.""" + config = { + "proximity": { + "home": { + "ignored_zones": ["work"], + "devices": ["device_tracker.test1"], + "tolerance": "1", + } + } + } + + assert await async_setup_component(hass, DOMAIN, config) + + hass.states.async_set("device_tracker.test1", "work", {"friendly_name": "test1"}) + await hass.async_block_till_done() + + # proximity entity + state = hass.states.get("proximity.home") + assert state.state == "not set" + assert state.attributes.get("nearest") == "not set" + assert state.attributes.get("dir_of_travel") == "not set" + + # sensor entities + state = hass.states.get("sensor.home_nearest") + assert state.state == STATE_UNKNOWN + + entity_base_name = f"sensor.home_{slugify('device_tracker.test1')}" + state = hass.states.get(f"{entity_base_name}_distance") + assert state.state == STATE_UNKNOWN + state = hass.states.get(f"{entity_base_name}_direction_of_travel") + assert state.state == STATE_UNKNOWN + + +async def test_device_tracker_test1_no_coordinates(hass: HomeAssistant) -> None: + """Test for tracker with no coordinates.""" + config = { + "proximity": { + "home": { + "ignored_zones": ["work"], + "devices": ["device_tracker.test1"], + "tolerance": "1", + } + } + } + + assert await async_setup_component(hass, DOMAIN, config) + + hass.states.async_set( + "device_tracker.test1", "not_home", {"friendly_name": "test1"} + ) + await hass.async_block_till_done() + + # proximity entity + state = hass.states.get("proximity.home") + assert state.attributes.get("nearest") == "not set" + assert state.attributes.get("dir_of_travel") == "not set" + + # sensor entities + state = hass.states.get("sensor.home_nearest") + assert state.state == STATE_UNKNOWN + + entity_base_name = f"sensor.home_{slugify('device_tracker.test1')}" + state = hass.states.get(f"{entity_base_name}_distance") + assert state.state == STATE_UNKNOWN + state = hass.states.get(f"{entity_base_name}_direction_of_travel") + assert state.state == STATE_UNKNOWN + + +async def test_device_tracker_test1_awayfurther_a_bit(hass: HomeAssistant) -> None: + """Test for tracker states.""" + assert await async_setup_component( + hass, + DOMAIN, + { + "proximity": { + "home": { + "ignored_zones": ["work"], + "devices": ["device_tracker.test1"], + "tolerance": 1000, + "zone": "home", + } + } + }, + ) + + hass.states.async_set( + "device_tracker.test1", + "not_home", + {"friendly_name": "test1", "latitude": 20.1000001, "longitude": 10.1000001}, + ) + await hass.async_block_till_done() + + # proximity entity + state = hass.states.get("proximity.home") + assert state.attributes.get("nearest") == "test1" + assert state.attributes.get("dir_of_travel") == "unknown" + + # sensor entities + state = hass.states.get("sensor.home_nearest") + assert state.state == "test1" + + entity_base_name = f"sensor.home_{slugify('device_tracker.test1')}" + state = hass.states.get(f"{entity_base_name}_distance") + assert state.state == "11912010" + state = hass.states.get(f"{entity_base_name}_direction_of_travel") + assert state.state == STATE_UNKNOWN + + hass.states.async_set( + "device_tracker.test1", + "not_home", + {"friendly_name": "test1", "latitude": 20.1000002, "longitude": 10.1000002}, + ) + await hass.async_block_till_done() + + # proximity entity + state = hass.states.get("proximity.home") + assert state.attributes.get("nearest") == "test1" + assert state.attributes.get("dir_of_travel") == "stationary" + + # sensor entities + state = hass.states.get("sensor.home_nearest") + assert state.state == "test1" + + entity_base_name = f"sensor.home_{slugify('device_tracker.test1')}" + state = hass.states.get(f"{entity_base_name}_distance") + assert state.state == "11912010" + state = hass.states.get(f"{entity_base_name}_direction_of_travel") + assert state.state == "stationary" + async def test_device_trackers_in_zone(hass: HomeAssistant) -> None: """Test for trackers in zone.""" @@ -135,6 +431,8 @@ async def test_device_trackers_in_zone(hass: HomeAssistant) -> None: {"friendly_name": "test2", "latitude": 2.1, "longitude": 1.1}, ) await hass.async_block_till_done() + + # proximity entity state = hass.states.get("proximity.home") assert state.state == "0" assert (state.attributes.get("nearest") == "test1, test2") or ( @@ -142,153 +440,16 @@ async def test_device_trackers_in_zone(hass: HomeAssistant) -> None: ) assert state.attributes.get("dir_of_travel") == "arrived" + # sensor entities + state = hass.states.get("sensor.home_nearest") + assert state.state == "test1, test2" -async def test_device_tracker_test1_away(hass: HomeAssistant) -> None: - """Test for tracker state away.""" - config = { - "proximity": { - "home": { - "ignored_zones": ["work"], - "devices": ["device_tracker.test1"], - "tolerance": "1", - } - } - } - - assert await async_setup_component(hass, DOMAIN, config) - - hass.states.async_set( - "device_tracker.test1", - "not_home", - {"friendly_name": "test1", "latitude": 20.1, "longitude": 10.1}, - ) - - await hass.async_block_till_done() - state = hass.states.get("proximity.home") - assert state.attributes.get("nearest") == "test1" - assert state.attributes.get("dir_of_travel") == "unknown" - - -async def test_device_tracker_test1_awayfurther(hass: HomeAssistant) -> None: - """Test for tracker state away further.""" - - config_zones(hass) - await hass.async_block_till_done() - - config = { - "proximity": { - "home": { - "ignored_zones": ["work"], - "devices": ["device_tracker.test1"], - "tolerance": "1", - } - } - } - - assert await async_setup_component(hass, DOMAIN, config) - - hass.states.async_set( - "device_tracker.test1", - "not_home", - {"friendly_name": "test1", "latitude": 20.1, "longitude": 10.1}, - ) - await hass.async_block_till_done() - state = hass.states.get("proximity.home") - assert state.attributes.get("nearest") == "test1" - assert state.attributes.get("dir_of_travel") == "unknown" - - hass.states.async_set( - "device_tracker.test1", - "not_home", - {"friendly_name": "test1", "latitude": 40.1, "longitude": 20.1}, - ) - await hass.async_block_till_done() - state = hass.states.get("proximity.home") - assert state.attributes.get("nearest") == "test1" - assert state.attributes.get("dir_of_travel") == "away_from" - - -async def test_device_tracker_test1_awaycloser(hass: HomeAssistant) -> None: - """Test for tracker state away closer.""" - config_zones(hass) - await hass.async_block_till_done() - - config = { - "proximity": { - "home": { - "ignored_zones": ["work"], - "devices": ["device_tracker.test1"], - "tolerance": "1", - } - } - } - - assert await async_setup_component(hass, DOMAIN, config) - - hass.states.async_set( - "device_tracker.test1", - "not_home", - {"friendly_name": "test1", "latitude": 40.1, "longitude": 20.1}, - ) - await hass.async_block_till_done() - state = hass.states.get("proximity.home") - assert state.attributes.get("nearest") == "test1" - assert state.attributes.get("dir_of_travel") == "unknown" - - hass.states.async_set( - "device_tracker.test1", - "not_home", - {"friendly_name": "test1", "latitude": 20.1, "longitude": 10.1}, - ) - await hass.async_block_till_done() - state = hass.states.get("proximity.home") - assert state.attributes.get("nearest") == "test1" - assert state.attributes.get("dir_of_travel") == "towards" - - -async def test_all_device_trackers_in_ignored_zone(hass: HomeAssistant) -> None: - """Test for tracker in ignored zone.""" - config = { - "proximity": { - "home": { - "ignored_zones": ["work"], - "devices": ["device_tracker.test1"], - "tolerance": "1", - } - } - } - - assert await async_setup_component(hass, DOMAIN, config) - - hass.states.async_set("device_tracker.test1", "work", {"friendly_name": "test1"}) - await hass.async_block_till_done() - state = hass.states.get("proximity.home") - assert state.state == "not set" - assert state.attributes.get("nearest") == "not set" - assert state.attributes.get("dir_of_travel") == "not set" - - -async def test_device_tracker_test1_no_coordinates(hass: HomeAssistant) -> None: - """Test for tracker with no coordinates.""" - config = { - "proximity": { - "home": { - "ignored_zones": ["work"], - "devices": ["device_tracker.test1"], - "tolerance": "1", - } - } - } - - assert await async_setup_component(hass, DOMAIN, config) - - hass.states.async_set( - "device_tracker.test1", "not_home", {"friendly_name": "test1"} - ) - await hass.async_block_till_done() - state = hass.states.get("proximity.home") - assert state.attributes.get("nearest") == "not set" - assert state.attributes.get("dir_of_travel") == "not set" + for device in ["device_tracker.test1", "device_tracker.test2"]: + entity_base_name = f"sensor.home_{slugify(device)}" + state = hass.states.get(f"{entity_base_name}_distance") + assert state.state == "0" + state = hass.states.get(f"{entity_base_name}_direction_of_travel") + assert state.state == "arrived" async def test_device_tracker_test1_awayfurther_than_test2_first_test1( @@ -328,20 +489,56 @@ async def test_device_tracker_test1_awayfurther_than_test2_first_test1( {"friendly_name": "test1", "latitude": 20.1, "longitude": 10.1}, ) await hass.async_block_till_done() + + # proximity entity state = hass.states.get("proximity.home") assert state.attributes.get("nearest") == "test1" assert state.attributes.get("dir_of_travel") == "unknown" + # sensor entities + state = hass.states.get("sensor.home_nearest") + assert state.state == "test1" + + entity_base_name = f"sensor.home_{slugify('device_tracker.test1')}" + state = hass.states.get(f"{entity_base_name}_distance") + assert state.state == "2218752" + state = hass.states.get(f"{entity_base_name}_direction_of_travel") + assert state.state == STATE_UNKNOWN + + entity_base_name = f"sensor.home_{slugify('device_tracker.test2')}" + state = hass.states.get(f"{entity_base_name}_distance") + assert state.state == STATE_UNKNOWN + state = hass.states.get(f"{entity_base_name}_direction_of_travel") + assert state.state == STATE_UNKNOWN + hass.states.async_set( "device_tracker.test2", "not_home", {"friendly_name": "test2", "latitude": 40.1, "longitude": 20.1}, ) await hass.async_block_till_done() + + # proximity entity state = hass.states.get("proximity.home") assert state.attributes.get("nearest") == "test1" assert state.attributes.get("dir_of_travel") == "unknown" + # sensor entities + state = hass.states.get("sensor.home_nearest") + assert state.state == "test1" + + entity_base_name = f"sensor.home_{slugify('device_tracker.test1')}" + state = hass.states.get(f"{entity_base_name}_distance") + assert state.state == "2218752" + state = hass.states.get(f"{entity_base_name}_direction_of_travel") + assert state.state == STATE_UNKNOWN + + entity_base_name = f"sensor.home_{slugify('device_tracker.test2')}" + state = hass.states.get(f"{entity_base_name}_distance") + assert state.state == "4625264" + state = hass.states.get(f"{entity_base_name}_direction_of_travel") + assert state.state == STATE_UNKNOWN + async def test_device_tracker_test1_awayfurther_than_test2_first_test2( hass: HomeAssistant, @@ -378,20 +575,56 @@ async def test_device_tracker_test1_awayfurther_than_test2_first_test2( {"friendly_name": "test2", "latitude": 40.1, "longitude": 20.1}, ) await hass.async_block_till_done() + + # proximity entity state = hass.states.get("proximity.home") assert state.attributes.get("nearest") == "test2" assert state.attributes.get("dir_of_travel") == "unknown" + # sensor entities + state = hass.states.get("sensor.home_nearest") + assert state.state == "test2" + + entity_base_name = f"sensor.home_{slugify('device_tracker.test1')}" + state = hass.states.get(f"{entity_base_name}_distance") + assert state.state == STATE_UNKNOWN + state = hass.states.get(f"{entity_base_name}_direction_of_travel") + assert state.state == STATE_UNKNOWN + + entity_base_name = f"sensor.home_{slugify('device_tracker.test2')}" + state = hass.states.get(f"{entity_base_name}_distance") + assert state.state == "4625264" + state = hass.states.get(f"{entity_base_name}_direction_of_travel") + assert state.state == STATE_UNKNOWN + hass.states.async_set( "device_tracker.test1", "not_home", {"friendly_name": "test1", "latitude": 20.1, "longitude": 10.1}, ) await hass.async_block_till_done() + + # proximity entity state = hass.states.get("proximity.home") assert state.attributes.get("nearest") == "test1" assert state.attributes.get("dir_of_travel") == "unknown" + # sensor entities + state = hass.states.get("sensor.home_nearest") + assert state.state == "test1" + + entity_base_name = f"sensor.home_{slugify('device_tracker.test1')}" + state = hass.states.get(f"{entity_base_name}_distance") + assert state.state == "2218752" + state = hass.states.get(f"{entity_base_name}_direction_of_travel") + assert state.state == STATE_UNKNOWN + + entity_base_name = f"sensor.home_{slugify('device_tracker.test2')}" + state = hass.states.get(f"{entity_base_name}_distance") + assert state.state == "4625264" + state = hass.states.get(f"{entity_base_name}_direction_of_travel") + assert state.state == STATE_UNKNOWN + async def test_device_tracker_test1_awayfurther_test2_in_ignored_zone( hass: HomeAssistant, @@ -423,10 +656,28 @@ async def test_device_tracker_test1_awayfurther_test2_in_ignored_zone( {"friendly_name": "test1", "latitude": 20.1, "longitude": 10.1}, ) await hass.async_block_till_done() + + # proximity entity state = hass.states.get("proximity.home") assert state.attributes.get("nearest") == "test1" assert state.attributes.get("dir_of_travel") == "unknown" + # sensor entities + state = hass.states.get("sensor.home_nearest") + assert state.state == "test1" + + entity_base_name = f"sensor.home_{slugify('device_tracker.test1')}" + state = hass.states.get(f"{entity_base_name}_distance") + assert state.state == "11912010" + state = hass.states.get(f"{entity_base_name}_direction_of_travel") + assert state.state == STATE_UNKNOWN + + entity_base_name = f"sensor.home_{slugify('device_tracker.test2')}" + state = hass.states.get(f"{entity_base_name}_distance") + assert state.state == STATE_UNKNOWN + state = hass.states.get(f"{entity_base_name}_direction_of_travel") + assert state.state == STATE_UNKNOWN + async def test_device_tracker_test1_awayfurther_test2_first( hass: HomeAssistant, @@ -489,47 +740,26 @@ async def test_device_tracker_test1_awayfurther_test2_first( hass.states.async_set("device_tracker.test1", "work", {"friendly_name": "test1"}) await hass.async_block_till_done() + # proximity entity state = hass.states.get("proximity.home") assert state.attributes.get("nearest") == "test2" assert state.attributes.get("dir_of_travel") == "unknown" + # sensor entities + state = hass.states.get("sensor.home_nearest") + assert state.state == "test2" -async def test_device_tracker_test1_awayfurther_a_bit(hass: HomeAssistant) -> None: - """Test for tracker states.""" - assert await async_setup_component( - hass, - DOMAIN, - { - "proximity": { - "home": { - "ignored_zones": ["work"], - "devices": ["device_tracker.test1"], - "tolerance": 1000, - "zone": "home", - } - } - }, - ) + entity_base_name = f"sensor.home_{slugify('device_tracker.test1')}" + state = hass.states.get(f"{entity_base_name}_distance") + assert state.state == STATE_UNKNOWN + state = hass.states.get(f"{entity_base_name}_direction_of_travel") + assert state.state == STATE_UNKNOWN - hass.states.async_set( - "device_tracker.test1", - "not_home", - {"friendly_name": "test1", "latitude": 20.1000001, "longitude": 10.1000001}, - ) - await hass.async_block_till_done() - state = hass.states.get("proximity.home") - assert state.attributes.get("nearest") == "test1" - assert state.attributes.get("dir_of_travel") == "unknown" - - hass.states.async_set( - "device_tracker.test1", - "not_home", - {"friendly_name": "test1", "latitude": 20.1000002, "longitude": 10.1000002}, - ) - await hass.async_block_till_done() - state = hass.states.get("proximity.home") - assert state.attributes.get("nearest") == "test1" - assert state.attributes.get("dir_of_travel") == "stationary" + entity_base_name = f"sensor.home_{slugify('device_tracker.test2')}" + state = hass.states.get(f"{entity_base_name}_distance") + assert state.state == "2218752" + state = hass.states.get(f"{entity_base_name}_direction_of_travel") + assert state.state == STATE_UNKNOWN async def test_device_tracker_test1_nearest_after_test2_in_ignored_zone( @@ -568,30 +798,84 @@ async def test_device_tracker_test1_nearest_after_test2_in_ignored_zone( {"friendly_name": "test1", "latitude": 20.1, "longitude": 10.1}, ) await hass.async_block_till_done() + + # proximity entity state = hass.states.get("proximity.home") assert state.attributes.get("nearest") == "test1" assert state.attributes.get("dir_of_travel") == "unknown" + # sensor entities + state = hass.states.get("sensor.home_nearest") + assert state.state == "test1" + + entity_base_name = f"sensor.home_{slugify('device_tracker.test1')}" + state = hass.states.get(f"{entity_base_name}_distance") + assert state.state == "2218752" + state = hass.states.get(f"{entity_base_name}_direction_of_travel") + assert state.state == STATE_UNKNOWN + + entity_base_name = f"sensor.home_{slugify('device_tracker.test2')}" + state = hass.states.get(f"{entity_base_name}_distance") + assert state.state == STATE_UNKNOWN + state = hass.states.get(f"{entity_base_name}_direction_of_travel") + assert state.state == STATE_UNKNOWN + hass.states.async_set( "device_tracker.test2", "not_home", {"friendly_name": "test2", "latitude": 10.1, "longitude": 5.1}, ) await hass.async_block_till_done() + + # proximity entity state = hass.states.get("proximity.home") assert state.attributes.get("nearest") == "test2" assert state.attributes.get("dir_of_travel") == "unknown" + # sensor entities + state = hass.states.get("sensor.home_nearest") + assert state.state == "test2" + + entity_base_name = f"sensor.home_{slugify('device_tracker.test1')}" + state = hass.states.get(f"{entity_base_name}_distance") + assert state.state == "2218752" + state = hass.states.get(f"{entity_base_name}_direction_of_travel") + assert state.state == STATE_UNKNOWN + + entity_base_name = f"sensor.home_{slugify('device_tracker.test2')}" + state = hass.states.get(f"{entity_base_name}_distance") + assert state.state == "989156" + state = hass.states.get(f"{entity_base_name}_direction_of_travel") + assert state.state == STATE_UNKNOWN + hass.states.async_set( "device_tracker.test2", "work", {"friendly_name": "test2", "latitude": 12.6, "longitude": 7.6}, ) await hass.async_block_till_done() + + # proximity entity state = hass.states.get("proximity.home") assert state.attributes.get("nearest") == "test1" assert state.attributes.get("dir_of_travel") == "unknown" + # sensor entities + state = hass.states.get("sensor.home_nearest") + assert state.state == "test1" + + entity_base_name = f"sensor.home_{slugify('device_tracker.test1')}" + state = hass.states.get(f"{entity_base_name}_distance") + assert state.state == "2218752" + state = hass.states.get(f"{entity_base_name}_direction_of_travel") + assert state.state == STATE_UNKNOWN + + entity_base_name = f"sensor.home_{slugify('device_tracker.test2')}" + state = hass.states.get(f"{entity_base_name}_distance") + assert state.state == "1364567" + state = hass.states.get(f"{entity_base_name}_direction_of_travel") + assert state.state == "away_from" + def config_zones(hass): """Set up zones for test."""