Skip to content

Commit

Permalink
Customise destination resolution and return the source of the resolut…
Browse files Browse the repository at this point in the history
…ion for use in the UI
  • Loading branch information
intrinseca committed Apr 13, 2024
1 parent 5cb9922 commit 429cb1b
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 51 deletions.
1 change: 1 addition & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,4 @@ repos:
- --pretty
- --show-error-codes
- --ignore-missing-imports
- --python-version=3.12
2 changes: 2 additions & 0 deletions config/configuration.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ logger:

input_text:
destination:
name: MDA Destination
initial: "home"

homeassistant:
auth_providers:
Expand Down
35 changes: 23 additions & 12 deletions custom_components/journey/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
For more details about this integration, please refer to
https://github.com/intrinseca/journey
"""

import asyncio
import logging
import math
Expand All @@ -13,7 +14,6 @@
from homeassistant.core import Config, Event, HomeAssistant
from homeassistant.helpers.debounce import Debouncer
from homeassistant.helpers.event import async_track_state_change_event
from homeassistant.helpers.location import find_coordinates
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed

from .api import JourneyApiClient
Expand All @@ -24,8 +24,7 @@
DOMAIN,
PLATFORMS,
)

# from .helpers import get_location_entity, get_location_from_attributes
from .helpers import find_coordinates

SCAN_INTERVAL = timedelta(minutes=5)

Expand Down Expand Up @@ -176,21 +175,33 @@ async def _handle_destination_state_change(self, event: Event):
async def update(self):
"""Update data via library."""
try:
origin = find_coordinates(self.hass, self._origin_entity_id)
destination = find_coordinates(self.hass, self._destination_entity_id)
origin_name, origin_coords = find_coordinates(
self.hass, self._origin_entity_id
)
destination_name, destination_coords = find_coordinates(
self.hass, self._destination_entity_id
)

if origin == destination:
if origin_coords == destination_coords:
_LOGGER.info("origin is equal to destination")
traveltime = JourneyTravelTime(
{"duration": {"value": 0}, "duration_in_traffic": {"value": 0}},
destination,
travel_time={
"duration": {"value": 0},
"duration_in_traffic": {"value": 0},
},
destination=origin_name,
)
else:
destination_addr, traveltime = await self.api.async_get_traveltime(
origin_coords, destination_coords
)

if destination_name is None:
destination_name = destination_addr.split(",")[0]

traveltime = JourneyTravelTime(
travel_time=await self.api.async_get_traveltime(
origin, destination
),
destination=destination,
travel_time=traveltime,
destination=destination_name,
)

return traveltime
Expand Down
3 changes: 2 additions & 1 deletion custom_components/journey/api.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Sample API Client."""

import asyncio
import logging
from datetime import datetime
Expand Down Expand Up @@ -30,7 +31,7 @@ def get_traveltime(self, origin: Coordinates, destination: Coordinates):
mode="driving",
departure_time=datetime.now(),
)
return result["rows"][0]["elements"][0]
return result["destination_addresses"][0], result["rows"][0]["elements"][0]
except Exception as exception: # pylint: disable=broad-except
_LOGGER.error("Failed to get distances - %s", exception)
return None
Expand Down
103 changes: 65 additions & 38 deletions custom_components/journey/helpers.py
Original file line number Diff line number Diff line change
@@ -1,54 +1,81 @@
"""Helpers for handling location entities."""

import logging

from homeassistant.const import ATTR_LATITUDE, ATTR_LONGITUDE
from homeassistant.core import HomeAssistant
from homeassistant.helpers import location

_LOGGER: logging.Logger = logging.getLogger(__package__)


def get_location_entity(hass, entity_id):
"""Get the location from the entity state or attributes."""

if (entity := hass.states.get(entity_id)) is None:
_LOGGER.error("Locating %s: Unable to find", entity_id)
raise ValueError("Invalid entity ID")

# Check if device is in a zone
if not entity_id.startswith("zone"):
if (zone_entity := hass.states.get(f"zone.{entity.state}")) is not None:
if location.has_location(zone_entity):
_LOGGER.debug(
"Locating %s: in %s, getting zone location",
entity_id,
zone_entity.entity_id,
)
return zone_entity
def find_coordinates(
hass: HomeAssistant, name: str, recursion_history: list | None = None
) -> tuple[str | None, str | None]:
"""Try to resolve the a location from a supplied name or entity_id.
_LOGGER.debug(
"Locating %s: in %s, no zone location",
entity_id,
zone_entity.entity_id,
)
else:
_LOGGER.debug("Locating %s: [zone '%s' not found]", entity_id, entity.state)
Will recursively resolve an entity if pointed to by the state of the supplied
entity.
# Check if the entity has location attributes
if location.has_location(entity):
_LOGGER.debug("Locating %s: from attributes", entity_id)
return entity
Returns coordinates in the form of '90.000,180.000', an address or
the state of the last resolved entity.
"""
# Check if a friendly name of a zone was supplied
if (zone_coords := location.resolve_zone(hass, name)) is not None:
_LOGGER.debug(
"%s, getting zone location",
name,
)
return (name, zone_coords)

# When everything fails just return nothing
return None
# Check if an entity_id was supplied.
if (entity_state := hass.states.get(name)) is None:
_LOGGER.debug("Unable to find entity %s", name)
return None, name

# Check if entity_state is a zone
zone_entity = hass.states.get(f"zone.{entity_state.state}")
if location.has_location(zone_entity): # type: ignore[arg-type]
_LOGGER.debug(
"%s is in %s, getting zone location",
name,
zone_entity.entity_id, # type: ignore[union-attr]
)
return zone_entity.name, location._get_location_from_attributes(zone_entity) # type: ignore[arg-type]

def get_location_from_attributes(entity):
"""Get the lat/long string from an entities attributes."""
attr = entity.attributes
return (float(attr.get(ATTR_LATITUDE)), float(attr.get(ATTR_LONGITUDE)))
# Check if entity_state is a friendly name of a zone
if (zone_coords := location.resolve_zone(hass, entity_state.state)) is not None:
_LOGGER.debug(
"%s is in %s, getting zone location",
name,
entity_state.state, # type: ignore[union-attr]
)
return entity_state.state, zone_coords

# Check if the entity_state has location attributes
if location.has_location(entity_state):
_LOGGER.debug("%s has coords", name)
return entity_state.name, location._get_location_from_attributes(entity_state)

def get_location_from_entity(hass, entity_id):
"""Get the location from the entity state or attributes."""
# Check if entity_state is an entity_id
if recursion_history is None:
recursion_history = []
recursion_history.append(name)
if entity_state.state in recursion_history:
_LOGGER.error(
(
"Circular reference detected while trying to find coordinates of an"
" entity. The state of %s has already been checked"
),
entity_state.state,
)
return None, None
_LOGGER.debug("Getting nested entity for state: %s", entity_state.state)
nested_entity = hass.states.get(entity_state.state)
if nested_entity is not None:
_LOGGER.debug("Resolving nested entity_id: %s", entity_state.state)
return find_coordinates(hass, entity_state.state, recursion_history)

return get_location_from_attributes(get_location_entity(hass, entity_id))
# Might be an address, coordinates or anything else.
# This has to be checked by the caller.
_LOGGER.debug("%s is in (raw state) '%s'", name, entity_state.state)
return None, entity_state.state

0 comments on commit 429cb1b

Please sign in to comment.