Use HERE API v8 (#80892)
* Use HERE API v8 Signed-off-by: Kevin Stillhammer <kevin.stillhammer@gmail.com> * Add migration Signed-off-by: Kevin Stillhammer <kevin.stillhammer@gmail.com> * Catch correct voluptuous error Signed-off-by: Kevin Stillhammer <kevin.stillhammer@gmail.com> * Use list comprehension for transit values * Add migration alternative Signed-off-by: Kevin Stillhammer <kevin.stillhammer@gmail.com>
This commit is contained in:
parent
495ca67e8b
commit
aedbfdabee
18 changed files with 1044 additions and 877 deletions
|
@ -1,38 +1,14 @@
|
|||
"""The HERE Travel Time integration."""
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, time, timedelta
|
||||
import logging
|
||||
|
||||
import async_timeout
|
||||
from herepy import NoRouteFoundError, RouteMode, RoutingApi, RoutingResponse
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
ATTR_ATTRIBUTION,
|
||||
CONF_API_KEY,
|
||||
CONF_MODE,
|
||||
CONF_UNIT_SYSTEM,
|
||||
LENGTH_METERS,
|
||||
LENGTH_MILES,
|
||||
Platform,
|
||||
)
|
||||
from homeassistant.const import CONF_API_KEY, CONF_MODE, CONF_UNIT_SYSTEM, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.location import find_coordinates
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
from homeassistant.util import dt
|
||||
from homeassistant.util.unit_conversion import DistanceConverter
|
||||
|
||||
from .const import (
|
||||
ATTR_DESTINATION,
|
||||
ATTR_DESTINATION_NAME,
|
||||
ATTR_DISTANCE,
|
||||
ATTR_DURATION,
|
||||
ATTR_DURATION_IN_TRAFFIC,
|
||||
ATTR_ORIGIN,
|
||||
ATTR_ORIGIN_NAME,
|
||||
CONF_ARRIVAL_TIME,
|
||||
CONF_DEPARTURE_TIME,
|
||||
CONF_DESTINATION_ENTITY_ID,
|
||||
|
@ -42,24 +18,22 @@ from .const import (
|
|||
CONF_ORIGIN_LATITUDE,
|
||||
CONF_ORIGIN_LONGITUDE,
|
||||
CONF_ROUTE_MODE,
|
||||
DEFAULT_SCAN_INTERVAL,
|
||||
DOMAIN,
|
||||
IMPERIAL_UNITS,
|
||||
NO_ROUTE_ERROR_MESSAGE,
|
||||
TRAFFIC_MODE_ENABLED,
|
||||
TRAVEL_MODES_VEHICLE,
|
||||
TRAVEL_MODE_PUBLIC,
|
||||
)
|
||||
from .model import HERERoutingData, HERETravelTimeConfig
|
||||
from .coordinator import (
|
||||
HERERoutingDataUpdateCoordinator,
|
||||
HERETransitDataUpdateCoordinator,
|
||||
)
|
||||
from .model import HERETravelTimeConfig
|
||||
|
||||
PLATFORMS = [Platform.SENSOR]
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
|
||||
"""Set up HERE Travel Time from a config entry."""
|
||||
api_key = config_entry.data[CONF_API_KEY]
|
||||
here_client = RoutingApi(api_key)
|
||||
|
||||
arrival = (
|
||||
dt.parse_time(config_entry.options[CONF_ARRIVAL_TIME])
|
||||
|
@ -86,12 +60,22 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
|
|||
departure=departure,
|
||||
)
|
||||
|
||||
coordinator = HereTravelTimeDataUpdateCoordinator(
|
||||
hass,
|
||||
here_client,
|
||||
here_travel_time_config,
|
||||
)
|
||||
hass.data.setdefault(DOMAIN, {})[config_entry.entry_id] = coordinator
|
||||
if config_entry.data[CONF_MODE] in {TRAVEL_MODE_PUBLIC, "publicTransportTimeTable"}:
|
||||
hass.data.setdefault(DOMAIN, {})[
|
||||
config_entry.entry_id
|
||||
] = HERETransitDataUpdateCoordinator(
|
||||
hass,
|
||||
api_key,
|
||||
here_travel_time_config,
|
||||
)
|
||||
else:
|
||||
hass.data.setdefault(DOMAIN, {})[
|
||||
config_entry.entry_id
|
||||
] = HERERoutingDataUpdateCoordinator(
|
||||
hass,
|
||||
api_key,
|
||||
here_travel_time_config,
|
||||
)
|
||||
await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS)
|
||||
|
||||
return True
|
||||
|
@ -106,173 +90,3 @@ async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) ->
|
|||
hass.data[DOMAIN].pop(config_entry.entry_id)
|
||||
|
||||
return unload_ok
|
||||
|
||||
|
||||
class HereTravelTimeDataUpdateCoordinator(DataUpdateCoordinator):
|
||||
"""HERETravelTime DataUpdateCoordinator."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
api: RoutingApi,
|
||||
config: HERETravelTimeConfig,
|
||||
) -> None:
|
||||
"""Initialize."""
|
||||
super().__init__(
|
||||
hass,
|
||||
_LOGGER,
|
||||
name=DOMAIN,
|
||||
update_interval=timedelta(seconds=DEFAULT_SCAN_INTERVAL),
|
||||
)
|
||||
self._api = api
|
||||
self.config = config
|
||||
|
||||
async def _async_update_data(self) -> HERERoutingData | None:
|
||||
"""Get the latest data from the HERE Routing API."""
|
||||
try:
|
||||
async with async_timeout.timeout(10):
|
||||
return await self.hass.async_add_executor_job(self._update)
|
||||
except NoRouteFoundError as error:
|
||||
raise UpdateFailed(NO_ROUTE_ERROR_MESSAGE) from error
|
||||
|
||||
def _update(self) -> HERERoutingData | None:
|
||||
"""Get the latest data from the HERE Routing API."""
|
||||
try:
|
||||
origin, destination, arrival, departure = self._prepare_parameters()
|
||||
|
||||
_LOGGER.debug(
|
||||
"Requesting route for origin: %s, destination: %s, route_mode: %s, mode: %s, traffic_mode: %s, arrival: %s, departure: %s",
|
||||
origin,
|
||||
destination,
|
||||
RouteMode[self.config.route_mode],
|
||||
RouteMode[self.config.travel_mode],
|
||||
RouteMode[TRAFFIC_MODE_ENABLED],
|
||||
arrival,
|
||||
departure,
|
||||
)
|
||||
|
||||
response: RoutingResponse = self._api.public_transport_timetable(
|
||||
origin,
|
||||
destination,
|
||||
True,
|
||||
[
|
||||
RouteMode[self.config.route_mode],
|
||||
RouteMode[self.config.travel_mode],
|
||||
RouteMode[TRAFFIC_MODE_ENABLED],
|
||||
],
|
||||
arrival=arrival,
|
||||
departure=departure,
|
||||
)
|
||||
|
||||
_LOGGER.debug("Raw response is: %s", response.response)
|
||||
|
||||
attribution: str | None = None
|
||||
if "sourceAttribution" in response.response:
|
||||
attribution = build_hass_attribution(
|
||||
response.response.get("sourceAttribution")
|
||||
)
|
||||
route: list = response.response["route"]
|
||||
summary: dict = route[0]["summary"]
|
||||
waypoint: list = route[0]["waypoint"]
|
||||
distance: float = summary["distance"]
|
||||
traffic_time: float = summary["baseTime"]
|
||||
if self.config.travel_mode in TRAVEL_MODES_VEHICLE:
|
||||
traffic_time = summary["trafficTime"]
|
||||
if self.config.units == IMPERIAL_UNITS:
|
||||
# Convert to miles.
|
||||
distance = DistanceConverter.convert(
|
||||
distance, LENGTH_METERS, LENGTH_MILES
|
||||
)
|
||||
else:
|
||||
# Convert to kilometers
|
||||
distance = distance / 1000
|
||||
return HERERoutingData(
|
||||
{
|
||||
ATTR_ATTRIBUTION: attribution,
|
||||
ATTR_DURATION: round(summary["baseTime"] / 60), # type: ignore[misc]
|
||||
ATTR_DURATION_IN_TRAFFIC: round(traffic_time / 60),
|
||||
ATTR_DISTANCE: distance,
|
||||
ATTR_ORIGIN: ",".join(origin),
|
||||
ATTR_DESTINATION: ",".join(destination),
|
||||
ATTR_ORIGIN_NAME: waypoint[0]["mappedRoadName"],
|
||||
ATTR_DESTINATION_NAME: waypoint[1]["mappedRoadName"],
|
||||
}
|
||||
)
|
||||
except InvalidCoordinatesException as ex:
|
||||
_LOGGER.error("Could not call HERE api: %s", ex)
|
||||
return None
|
||||
|
||||
def _prepare_parameters(
|
||||
self,
|
||||
) -> tuple[list[str], list[str], str | None, str | None]:
|
||||
"""Prepare parameters for the HERE api."""
|
||||
|
||||
def _from_entity_id(entity_id: str) -> list[str]:
|
||||
coordinates = find_coordinates(self.hass, entity_id)
|
||||
if coordinates is None:
|
||||
raise InvalidCoordinatesException(
|
||||
f"No coordinatnes found for {entity_id}"
|
||||
)
|
||||
try:
|
||||
here_formatted_coordinates = coordinates.split(",")
|
||||
vol.Schema(cv.gps(here_formatted_coordinates))
|
||||
except (AttributeError, vol.Invalid) as ex:
|
||||
raise InvalidCoordinatesException(
|
||||
f"{coordinates} are not valid coordinates"
|
||||
) from ex
|
||||
return here_formatted_coordinates
|
||||
|
||||
# Destination
|
||||
if self.config.destination_entity_id is not None:
|
||||
destination = _from_entity_id(self.config.destination_entity_id)
|
||||
else:
|
||||
destination = [
|
||||
str(self.config.destination_latitude),
|
||||
str(self.config.destination_longitude),
|
||||
]
|
||||
|
||||
# Origin
|
||||
if self.config.origin_entity_id is not None:
|
||||
origin = _from_entity_id(self.config.origin_entity_id)
|
||||
else:
|
||||
origin = [
|
||||
str(self.config.origin_latitude),
|
||||
str(self.config.origin_longitude),
|
||||
]
|
||||
|
||||
# Arrival/Departure
|
||||
arrival: str | None = None
|
||||
departure: str | None = None
|
||||
if self.config.arrival is not None:
|
||||
arrival = convert_time_to_isodate(self.config.arrival)
|
||||
if self.config.departure is not None:
|
||||
departure = convert_time_to_isodate(self.config.departure)
|
||||
|
||||
if arrival is None and departure is None:
|
||||
departure = "now"
|
||||
|
||||
return (origin, destination, arrival, departure)
|
||||
|
||||
|
||||
def build_hass_attribution(source_attribution: dict) -> str | None:
|
||||
"""Build a hass frontend ready string out of the sourceAttribution."""
|
||||
if (suppliers := source_attribution.get("supplier")) is not None:
|
||||
supplier_titles = []
|
||||
for supplier in suppliers:
|
||||
if (title := supplier.get("title")) is not None:
|
||||
supplier_titles.append(title)
|
||||
joined_supplier_titles = ",".join(supplier_titles)
|
||||
return f"With the support of {joined_supplier_titles}. All information is provided without warranty of any kind."
|
||||
return None
|
||||
|
||||
|
||||
def convert_time_to_isodate(simple_time: time) -> str:
|
||||
"""Take a time like 08:00:00 and combine it with the current date."""
|
||||
combined = datetime.combine(dt.start_of_local_day(), simple_time)
|
||||
if combined < datetime.now():
|
||||
combined = combined + timedelta(days=1)
|
||||
return combined.isoformat()
|
||||
|
||||
|
||||
class InvalidCoordinatesException(Exception):
|
||||
"""Coordinates for origin or destination are malformed."""
|
||||
|
|
|
@ -4,7 +4,14 @@ from __future__ import annotations
|
|||
import logging
|
||||
from typing import Any
|
||||
|
||||
from herepy import HEREError, InvalidCredentialsError, RouteMode, RoutingApi
|
||||
from here_routing import (
|
||||
HERERoutingApi,
|
||||
HERERoutingError,
|
||||
HERERoutingUnauthorizedError,
|
||||
Place,
|
||||
TransportMode,
|
||||
)
|
||||
from here_transit import HERETransitError
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import config_entries
|
||||
|
@ -38,17 +45,14 @@ from .const import (
|
|||
CONF_ORIGIN_LATITUDE,
|
||||
CONF_ORIGIN_LONGITUDE,
|
||||
CONF_ROUTE_MODE,
|
||||
CONF_TRAFFIC_MODE,
|
||||
DEFAULT_NAME,
|
||||
DOMAIN,
|
||||
IMPERIAL_UNITS,
|
||||
METRIC_UNITS,
|
||||
ROUTE_MODE_FASTEST,
|
||||
ROUTE_MODES,
|
||||
TRAFFIC_MODE_ENABLED,
|
||||
TRAFFIC_MODES,
|
||||
TRAVEL_MODE_CAR,
|
||||
TRAVEL_MODE_PUBLIC_TIME_TABLE,
|
||||
TRAVEL_MODE_PUBLIC,
|
||||
TRAVEL_MODES,
|
||||
UNITS,
|
||||
)
|
||||
|
@ -56,26 +60,23 @@ from .const import (
|
|||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def validate_api_key(api_key: str) -> None:
|
||||
async def async_validate_api_key(api_key: str) -> None:
|
||||
"""Validate the user input allows us to connect."""
|
||||
known_working_origin = [38.9, -77.04833]
|
||||
known_working_destination = [39.0, -77.1]
|
||||
RoutingApi(api_key).public_transport_timetable(
|
||||
known_working_origin,
|
||||
known_working_destination,
|
||||
True,
|
||||
[
|
||||
RouteMode[ROUTE_MODE_FASTEST],
|
||||
RouteMode[TRAVEL_MODE_CAR],
|
||||
RouteMode[TRAFFIC_MODE_ENABLED],
|
||||
],
|
||||
arrival=None,
|
||||
departure="now",
|
||||
known_working_origin = Place(latitude=38.9, longitude=-77.04833)
|
||||
known_working_destination = Place(latitude=39.0, longitude=-77.1)
|
||||
|
||||
await HERERoutingApi(api_key).route(
|
||||
origin=known_working_origin,
|
||||
destination=known_working_destination,
|
||||
transport_mode=TransportMode.CAR,
|
||||
)
|
||||
|
||||
|
||||
def get_user_step_schema(data: dict[str, Any]) -> vol.Schema:
|
||||
"""Get a populated schema or default."""
|
||||
travel_mode = data.get(CONF_MODE, TRAVEL_MODE_CAR)
|
||||
if travel_mode == "publicTransportTimeTable":
|
||||
travel_mode = TRAVEL_MODE_PUBLIC
|
||||
return vol.Schema(
|
||||
{
|
||||
vol.Optional(
|
||||
|
@ -92,7 +93,6 @@ def get_user_step_schema(data: dict[str, Any]) -> vol.Schema:
|
|||
def default_options(hass: HomeAssistant) -> dict[str, str | None]:
|
||||
"""Get the default options."""
|
||||
default = {
|
||||
CONF_TRAFFIC_MODE: TRAFFIC_MODE_ENABLED,
|
||||
CONF_ROUTE_MODE: ROUTE_MODE_FASTEST,
|
||||
CONF_ARRIVAL_TIME: None,
|
||||
CONF_DEPARTURE_TIME: None,
|
||||
|
@ -128,12 +128,10 @@ class HERETravelTimeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
user_input = user_input or {}
|
||||
if user_input:
|
||||
try:
|
||||
await self.hass.async_add_executor_job(
|
||||
validate_api_key, user_input[CONF_API_KEY]
|
||||
)
|
||||
except InvalidCredentialsError:
|
||||
await async_validate_api_key(user_input[CONF_API_KEY])
|
||||
except HERERoutingUnauthorizedError:
|
||||
errors["base"] = "invalid_auth"
|
||||
except HEREError as error:
|
||||
except (HERERoutingError, HERETransitError) as error:
|
||||
_LOGGER.exception("Unexpected exception: %s", error)
|
||||
errors["base"] = "unknown"
|
||||
if not errors:
|
||||
|
@ -251,25 +249,14 @@ class HERETravelTimeOptionsFlow(config_entries.OptionsFlow):
|
|||
"""Manage the HERE Travel Time options."""
|
||||
if user_input is not None:
|
||||
self._config = user_input
|
||||
if self.config_entry.data[CONF_MODE] == TRAVEL_MODE_PUBLIC_TIME_TABLE:
|
||||
return self.async_show_menu(
|
||||
step_id="time_menu",
|
||||
menu_options=["departure_time", "arrival_time", "no_time"],
|
||||
)
|
||||
return self.async_show_menu(
|
||||
step_id="time_menu",
|
||||
menu_options=["departure_time", "no_time"],
|
||||
menu_options=["departure_time", "arrival_time", "no_time"],
|
||||
)
|
||||
|
||||
defaults = default_options(self.hass)
|
||||
schema = vol.Schema(
|
||||
{
|
||||
vol.Optional(
|
||||
CONF_TRAFFIC_MODE,
|
||||
default=self.config_entry.options.get(
|
||||
CONF_TRAFFIC_MODE, defaults[CONF_TRAFFIC_MODE]
|
||||
),
|
||||
): vol.In(TRAFFIC_MODES),
|
||||
vol.Optional(
|
||||
CONF_ROUTE_MODE,
|
||||
default=self.config_entry.options.get(
|
||||
|
|
|
@ -11,7 +11,6 @@ CONF_ORIGIN = "origin"
|
|||
CONF_ORIGIN_LATITUDE = "origin_latitude"
|
||||
CONF_ORIGIN_LONGITUDE = "origin_longitude"
|
||||
CONF_ORIGIN_ENTITY_ID = "origin_entity_id"
|
||||
CONF_TRAFFIC_MODE = "traffic_mode"
|
||||
CONF_ROUTE_MODE = "route_mode"
|
||||
CONF_ARRIVAL = "arrival"
|
||||
CONF_DEPARTURE = "departure"
|
||||
|
@ -24,23 +23,17 @@ TRAVEL_MODE_BICYCLE = "bicycle"
|
|||
TRAVEL_MODE_CAR = "car"
|
||||
TRAVEL_MODE_PEDESTRIAN = "pedestrian"
|
||||
TRAVEL_MODE_PUBLIC = "publicTransport"
|
||||
TRAVEL_MODE_PUBLIC_TIME_TABLE = "publicTransportTimeTable"
|
||||
TRAVEL_MODE_TRUCK = "truck"
|
||||
TRAVEL_MODES = [
|
||||
TRAVEL_MODE_BICYCLE,
|
||||
TRAVEL_MODE_CAR,
|
||||
TRAVEL_MODE_PEDESTRIAN,
|
||||
TRAVEL_MODE_PUBLIC,
|
||||
TRAVEL_MODE_PUBLIC_TIME_TABLE,
|
||||
TRAVEL_MODE_TRUCK,
|
||||
]
|
||||
|
||||
TRAVEL_MODES_VEHICLE = [TRAVEL_MODE_CAR, TRAVEL_MODE_TRUCK]
|
||||
|
||||
TRAFFIC_MODE_ENABLED = "traffic_enabled"
|
||||
TRAFFIC_MODE_DISABLED = "traffic_disabled"
|
||||
TRAFFIC_MODES = [TRAFFIC_MODE_ENABLED, TRAFFIC_MODE_DISABLED]
|
||||
|
||||
ROUTE_MODE_FASTEST = "fastest"
|
||||
ROUTE_MODE_SHORTEST = "shortest"
|
||||
ROUTE_MODES = [ROUTE_MODE_FASTEST, ROUTE_MODE_SHORTEST]
|
||||
|
@ -55,7 +48,6 @@ ICONS = {
|
|||
TRAVEL_MODE_BICYCLE: ICON_BICYCLE,
|
||||
TRAVEL_MODE_PEDESTRIAN: ICON_PEDESTRIAN,
|
||||
TRAVEL_MODE_PUBLIC: ICON_PUBLIC,
|
||||
TRAVEL_MODE_PUBLIC_TIME_TABLE: ICON_PUBLIC,
|
||||
TRAVEL_MODE_TRUCK: ICON_TRUCK,
|
||||
}
|
||||
|
||||
|
@ -69,10 +61,7 @@ ATTR_ORIGIN = "origin"
|
|||
ATTR_DESTINATION = "destination"
|
||||
|
||||
ATTR_UNIT_SYSTEM = "unit_system"
|
||||
ATTR_TRAFFIC_MODE = CONF_TRAFFIC_MODE
|
||||
|
||||
ATTR_DURATION_IN_TRAFFIC = "duration_in_traffic"
|
||||
ATTR_ORIGIN_NAME = "origin_name"
|
||||
ATTR_DESTINATION_NAME = "destination_name"
|
||||
|
||||
NO_ROUTE_ERROR_MESSAGE = "HERE could not find a route based on the input"
|
||||
|
|
290
homeassistant/components/here_travel_time/coordinator.py
Normal file
290
homeassistant/components/here_travel_time/coordinator.py
Normal file
|
@ -0,0 +1,290 @@
|
|||
"""The HERE Travel Time integration."""
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, time, timedelta
|
||||
import logging
|
||||
|
||||
import here_routing
|
||||
from here_routing import HERERoutingApi, Return, RoutingMode, Spans, TransportMode
|
||||
import here_transit
|
||||
from here_transit import HERETransitApi
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.const import ATTR_ATTRIBUTION, LENGTH_METERS, LENGTH_MILES
|
||||
from homeassistant.core import HomeAssistant
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.location import find_coordinates
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||
from homeassistant.util import dt
|
||||
from homeassistant.util.unit_conversion import DistanceConverter
|
||||
|
||||
from .const import (
|
||||
ATTR_DESTINATION,
|
||||
ATTR_DESTINATION_NAME,
|
||||
ATTR_DISTANCE,
|
||||
ATTR_DURATION,
|
||||
ATTR_DURATION_IN_TRAFFIC,
|
||||
ATTR_ORIGIN,
|
||||
ATTR_ORIGIN_NAME,
|
||||
DEFAULT_SCAN_INTERVAL,
|
||||
DOMAIN,
|
||||
IMPERIAL_UNITS,
|
||||
ROUTE_MODE_FASTEST,
|
||||
)
|
||||
from .model import HERETravelTimeConfig, HERETravelTimeData
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class HERERoutingDataUpdateCoordinator(DataUpdateCoordinator):
|
||||
"""here_routing DataUpdateCoordinator."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
api_key: str,
|
||||
config: HERETravelTimeConfig,
|
||||
) -> None:
|
||||
"""Initialize."""
|
||||
super().__init__(
|
||||
hass,
|
||||
_LOGGER,
|
||||
name=DOMAIN,
|
||||
update_interval=timedelta(seconds=DEFAULT_SCAN_INTERVAL),
|
||||
)
|
||||
self._api = HERERoutingApi(api_key)
|
||||
self.config = config
|
||||
|
||||
async def _async_update_data(self) -> HERETravelTimeData | None:
|
||||
"""Get the latest data from the HERE Routing API."""
|
||||
origin, destination, arrival, departure = prepare_parameters(
|
||||
self.hass, self.config
|
||||
)
|
||||
|
||||
route_mode = (
|
||||
RoutingMode.FAST
|
||||
if self.config.route_mode == ROUTE_MODE_FASTEST
|
||||
else RoutingMode.SHORT
|
||||
)
|
||||
|
||||
_LOGGER.debug(
|
||||
"Requesting route for origin: %s, destination: %s, route_mode: %s, mode: %s, arrival: %s, departure: %s",
|
||||
origin,
|
||||
destination,
|
||||
route_mode,
|
||||
TransportMode(self.config.travel_mode),
|
||||
arrival,
|
||||
departure,
|
||||
)
|
||||
|
||||
response = await self._api.route(
|
||||
transport_mode=TransportMode(self.config.travel_mode),
|
||||
origin=here_routing.Place(origin[0], origin[1]),
|
||||
destination=here_routing.Place(destination[0], destination[1]),
|
||||
routing_mode=route_mode,
|
||||
arrival_time=arrival,
|
||||
departure_time=departure,
|
||||
return_values=[Return.POLYINE, Return.SUMMARY],
|
||||
spans=[Spans.NAMES],
|
||||
)
|
||||
|
||||
_LOGGER.debug("Raw response is: %s", response)
|
||||
|
||||
return self._parse_routing_response(response)
|
||||
|
||||
def _parse_routing_response(self, response) -> HERETravelTimeData:
|
||||
"""Parse the routing response dict to a HERETravelTimeData."""
|
||||
section: dict = response["routes"][0]["sections"][0]
|
||||
summary: dict = section["summary"]
|
||||
mapped_origin_lat: float = section["departure"]["place"]["location"]["lat"]
|
||||
mapped_origin_lon: float = section["departure"]["place"]["location"]["lng"]
|
||||
mapped_destination_lat: float = section["arrival"]["place"]["location"]["lat"]
|
||||
mapped_destination_lon: float = section["arrival"]["place"]["location"]["lng"]
|
||||
distance: float = summary["length"]
|
||||
if self.config.units == IMPERIAL_UNITS:
|
||||
# Convert to miles.
|
||||
distance = DistanceConverter.convert(distance, LENGTH_METERS, LENGTH_MILES)
|
||||
else:
|
||||
# Convert to kilometers
|
||||
distance = distance / 1000
|
||||
origin_name: str | None = None
|
||||
if (names := section["spans"][0].get("names")) is not None:
|
||||
origin_name = names[0]["value"]
|
||||
destination_name: str | None = None
|
||||
if (names := section["spans"][-1].get("names")) is not None:
|
||||
destination_name = names[0]["value"]
|
||||
return HERETravelTimeData(
|
||||
{
|
||||
ATTR_ATTRIBUTION: None,
|
||||
ATTR_DURATION: round(summary["baseDuration"] / 60), # type: ignore[misc]
|
||||
ATTR_DURATION_IN_TRAFFIC: round(summary["duration"] / 60),
|
||||
ATTR_DISTANCE: distance,
|
||||
ATTR_ORIGIN: f"{mapped_origin_lat},{mapped_origin_lon}",
|
||||
ATTR_DESTINATION: f"{mapped_destination_lat},{mapped_destination_lon}",
|
||||
ATTR_ORIGIN_NAME: origin_name,
|
||||
ATTR_DESTINATION_NAME: destination_name,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class HERETransitDataUpdateCoordinator(DataUpdateCoordinator):
|
||||
"""HERETravelTime DataUpdateCoordinator."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
api_key: str,
|
||||
config: HERETravelTimeConfig,
|
||||
) -> None:
|
||||
"""Initialize."""
|
||||
super().__init__(
|
||||
hass,
|
||||
_LOGGER,
|
||||
name=DOMAIN,
|
||||
update_interval=timedelta(seconds=DEFAULT_SCAN_INTERVAL),
|
||||
)
|
||||
self._api = HERETransitApi(api_key)
|
||||
self.config = config
|
||||
|
||||
async def _async_update_data(self) -> HERETravelTimeData | None:
|
||||
"""Get the latest data from the HERE Routing API."""
|
||||
origin, destination, arrival, departure = prepare_parameters(
|
||||
self.hass, self.config
|
||||
)
|
||||
|
||||
_LOGGER.debug(
|
||||
"Requesting transit route for origin: %s, destination: %s, arrival: %s, departure: %s",
|
||||
origin,
|
||||
destination,
|
||||
arrival,
|
||||
departure,
|
||||
)
|
||||
|
||||
response = await self._api.route(
|
||||
origin=here_transit.Place(latitude=origin[0], longitude=origin[1]),
|
||||
destination=here_transit.Place(
|
||||
latitude=destination[0], longitude=destination[1]
|
||||
),
|
||||
arrival_time=arrival,
|
||||
departure_time=departure,
|
||||
return_values=[
|
||||
here_transit.Return.POLYLINE,
|
||||
here_transit.Return.TRAVEL_SUMMARY,
|
||||
],
|
||||
)
|
||||
|
||||
_LOGGER.debug("Raw response is: %s", response)
|
||||
|
||||
return self._parse_transit_response(response)
|
||||
|
||||
def _parse_transit_response(self, response) -> HERETravelTimeData:
|
||||
"""Parse the transit response dict to a HERETravelTimeData."""
|
||||
sections: dict = response["routes"][0]["sections"]
|
||||
attribution: str | None = build_hass_attribution(sections)
|
||||
mapped_origin_lat: float = sections[0]["departure"]["place"]["location"]["lat"]
|
||||
mapped_origin_lon: float = sections[0]["departure"]["place"]["location"]["lng"]
|
||||
mapped_destination_lat: float = sections[-1]["arrival"]["place"]["location"][
|
||||
"lat"
|
||||
]
|
||||
mapped_destination_lon: float = sections[-1]["arrival"]["place"]["location"][
|
||||
"lng"
|
||||
]
|
||||
distance: float = sum(
|
||||
section["travelSummary"]["length"] for section in sections
|
||||
)
|
||||
duration: float = sum(
|
||||
section["travelSummary"]["duration"] for section in sections
|
||||
)
|
||||
if self.config.units == IMPERIAL_UNITS:
|
||||
# Convert to miles.
|
||||
distance = DistanceConverter.convert(distance, LENGTH_METERS, LENGTH_MILES)
|
||||
else:
|
||||
# Convert to kilometers
|
||||
distance = distance / 1000
|
||||
return HERETravelTimeData(
|
||||
{
|
||||
ATTR_ATTRIBUTION: attribution,
|
||||
ATTR_DURATION: round(duration / 60), # type: ignore[misc]
|
||||
ATTR_DURATION_IN_TRAFFIC: round(duration / 60),
|
||||
ATTR_DISTANCE: distance,
|
||||
ATTR_ORIGIN: f"{mapped_origin_lat},{mapped_origin_lon}",
|
||||
ATTR_DESTINATION: f"{mapped_destination_lat},{mapped_destination_lon}",
|
||||
ATTR_ORIGIN_NAME: sections[0]["departure"]["place"].get("name"),
|
||||
ATTR_DESTINATION_NAME: sections[-1]["arrival"]["place"].get("name"),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def prepare_parameters(
|
||||
hass: HomeAssistant,
|
||||
config: HERETravelTimeConfig,
|
||||
) -> tuple[list[str], list[str], str | None, str | None]:
|
||||
"""Prepare parameters for the HERE api."""
|
||||
|
||||
def _from_entity_id(entity_id: str) -> list[str]:
|
||||
coordinates = find_coordinates(hass, entity_id)
|
||||
if coordinates is None:
|
||||
raise InvalidCoordinatesException(f"No coordinates found for {entity_id}")
|
||||
try:
|
||||
formatted_coordinates = coordinates.split(",")
|
||||
vol.Schema(cv.gps(formatted_coordinates))
|
||||
except (AttributeError, vol.ExactSequenceInvalid) as ex:
|
||||
raise InvalidCoordinatesException(
|
||||
f"{coordinates} are not valid coordinates"
|
||||
) from ex
|
||||
return formatted_coordinates
|
||||
|
||||
# Destination
|
||||
if config.destination_entity_id is not None:
|
||||
destination = _from_entity_id(config.destination_entity_id)
|
||||
else:
|
||||
destination = [
|
||||
str(config.destination_latitude),
|
||||
str(config.destination_longitude),
|
||||
]
|
||||
|
||||
# Origin
|
||||
if config.origin_entity_id is not None:
|
||||
origin = _from_entity_id(config.origin_entity_id)
|
||||
else:
|
||||
origin = [
|
||||
str(config.origin_latitude),
|
||||
str(config.origin_longitude),
|
||||
]
|
||||
|
||||
# Arrival/Departure
|
||||
arrival: str | None = None
|
||||
departure: str | None = None
|
||||
if config.arrival is not None:
|
||||
arrival = convert_time_to_isodate(config.arrival)
|
||||
if config.departure is not None:
|
||||
departure = convert_time_to_isodate(config.departure)
|
||||
|
||||
return (origin, destination, arrival, departure)
|
||||
|
||||
|
||||
def build_hass_attribution(sections: dict) -> str | None:
|
||||
"""Build a hass frontend ready string out of the attributions."""
|
||||
relevant_attributions = []
|
||||
for section in sections:
|
||||
if (attributions := section.get("attributions")) is not None:
|
||||
for attribution in attributions:
|
||||
if (href := attribution.get("href")) is not None:
|
||||
relevant_attributions.append(f"{href}")
|
||||
if (text := attribution.get("text")) is not None:
|
||||
relevant_attributions.append(text)
|
||||
if len(relevant_attributions) > 0:
|
||||
return ",".join(relevant_attributions)
|
||||
return None
|
||||
|
||||
|
||||
def convert_time_to_isodate(simple_time: time) -> str:
|
||||
"""Take a time like 08:00:00 and combine it with the current date."""
|
||||
combined = datetime.combine(dt.start_of_local_day(), simple_time)
|
||||
if combined < datetime.now():
|
||||
combined = combined + timedelta(days=1)
|
||||
return combined.isoformat()
|
||||
|
||||
|
||||
class InvalidCoordinatesException(Exception):
|
||||
"""Coordinates for origin or destination are malformed."""
|
|
@ -3,8 +3,8 @@
|
|||
"name": "HERE Travel Time",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/here_travel_time",
|
||||
"requirements": ["herepy==2.0.0"],
|
||||
"requirements": ["here_routing==0.1.1", "here_transit==1.0.0"],
|
||||
"codeowners": ["@eifinger"],
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["herepy"]
|
||||
"loggers": ["here_routing", "here_transit"]
|
||||
}
|
||||
|
|
|
@ -6,8 +6,8 @@ from datetime import time
|
|||
from typing import TypedDict
|
||||
|
||||
|
||||
class HERERoutingData(TypedDict):
|
||||
"""Routing information calculated from a herepy.RoutingResponse."""
|
||||
class HERETravelTimeData(TypedDict):
|
||||
"""Routing information."""
|
||||
|
||||
ATTR_ATTRIBUTION: str | None
|
||||
ATTR_DURATION: float
|
||||
|
|
|
@ -28,7 +28,6 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
|||
from homeassistant.helpers.start import async_at_start
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from . import HereTravelTimeDataUpdateCoordinator
|
||||
from .const import (
|
||||
ATTR_DESTINATION,
|
||||
ATTR_DESTINATION_NAME,
|
||||
|
@ -42,6 +41,7 @@ from .const import (
|
|||
ICONS,
|
||||
IMPERIAL_UNITS,
|
||||
)
|
||||
from .coordinator import HERERoutingDataUpdateCoordinator
|
||||
|
||||
SCAN_INTERVAL = timedelta(minutes=5)
|
||||
|
||||
|
@ -101,7 +101,7 @@ class HERETravelTimeSensor(SensorEntity, CoordinatorEntity):
|
|||
unique_id_prefix: str,
|
||||
name: str,
|
||||
sensor_description: SensorEntityDescription,
|
||||
coordinator: HereTravelTimeDataUpdateCoordinator,
|
||||
coordinator: HERERoutingDataUpdateCoordinator,
|
||||
) -> None:
|
||||
"""Initialize the sensor."""
|
||||
super().__init__(coordinator)
|
||||
|
@ -146,7 +146,7 @@ class OriginSensor(HERETravelTimeSensor):
|
|||
self,
|
||||
unique_id_prefix: str,
|
||||
name: str,
|
||||
coordinator: HereTravelTimeDataUpdateCoordinator,
|
||||
coordinator: HERERoutingDataUpdateCoordinator,
|
||||
) -> None:
|
||||
"""Initialize the sensor."""
|
||||
sensor_description = SensorEntityDescription(
|
||||
|
@ -174,7 +174,7 @@ class DestinationSensor(HERETravelTimeSensor):
|
|||
self,
|
||||
unique_id_prefix: str,
|
||||
name: str,
|
||||
coordinator: HereTravelTimeDataUpdateCoordinator,
|
||||
coordinator: HERERoutingDataUpdateCoordinator,
|
||||
) -> None:
|
||||
"""Initialize the sensor."""
|
||||
sensor_description = SensorEntityDescription(
|
||||
|
@ -202,7 +202,7 @@ class DistanceSensor(HERETravelTimeSensor):
|
|||
self,
|
||||
unique_id_prefix: str,
|
||||
name: str,
|
||||
coordinator: HereTravelTimeDataUpdateCoordinator,
|
||||
coordinator: HERERoutingDataUpdateCoordinator,
|
||||
) -> None:
|
||||
"""Initialize the sensor."""
|
||||
sensor_description = SensorEntityDescription(
|
||||
|
|
|
@ -868,7 +868,10 @@ hdate==0.10.4
|
|||
heatmiserV3==1.1.18
|
||||
|
||||
# homeassistant.components.here_travel_time
|
||||
herepy==2.0.0
|
||||
here_routing==0.1.1
|
||||
|
||||
# homeassistant.components.here_travel_time
|
||||
here_transit==1.0.0
|
||||
|
||||
# homeassistant.components.hikvisioncam
|
||||
hikvision==0.4
|
||||
|
|
|
@ -654,7 +654,10 @@ hatasmota==0.6.1
|
|||
hdate==0.10.4
|
||||
|
||||
# homeassistant.components.here_travel_time
|
||||
herepy==2.0.0
|
||||
here_routing==0.1.1
|
||||
|
||||
# homeassistant.components.here_travel_time
|
||||
here_transit==1.0.0
|
||||
|
||||
# homeassistant.components.hlk_sw16
|
||||
hlk-sw16==0.0.9
|
||||
|
|
|
@ -2,37 +2,39 @@
|
|||
import json
|
||||
from unittest.mock import patch
|
||||
|
||||
from herepy.models import RoutingResponse
|
||||
import pytest
|
||||
|
||||
from tests.common import load_fixture
|
||||
|
||||
RESPONSE = RoutingResponse.new_from_jsondict(
|
||||
json.loads(load_fixture("here_travel_time/car_response.json"))
|
||||
RESPONSE = json.loads(load_fixture("here_travel_time/car_response.json"))
|
||||
TRANSIT_RESPONSE = json.loads(
|
||||
load_fixture("here_travel_time/transit_route_response.json")
|
||||
)
|
||||
RESPONSE.route_short = "US-29 - K St NW; US-29 - Whitehurst Fwy; I-495 N - Capital Beltway; MD-187 S - Old Georgetown Rd"
|
||||
|
||||
EMPTY_ATTRIBUTION_RESPONSE = RoutingResponse.new_from_jsondict(
|
||||
json.loads(load_fixture("here_travel_time/empty_attribution_response.json"))
|
||||
NO_ATTRIBUTION_TRANSIT_RESPONSE = json.loads(
|
||||
load_fixture("here_travel_time/no_attribution_transit_route_response.json")
|
||||
)
|
||||
EMPTY_ATTRIBUTION_RESPONSE.route_short = "US-29 - K St NW; US-29 - Whitehurst Fwy; I-495 N - Capital Beltway; MD-187 S - Old Georgetown Rd"
|
||||
|
||||
|
||||
@pytest.fixture(name="valid_response")
|
||||
def valid_response_fixture():
|
||||
"""Return valid api response."""
|
||||
with patch(
|
||||
"herepy.RoutingApi.public_transport_timetable",
|
||||
"here_transit.HERETransitApi.route", return_value=TRANSIT_RESPONSE
|
||||
), patch(
|
||||
"here_routing.HERERoutingApi.route",
|
||||
return_value=RESPONSE,
|
||||
) as mock:
|
||||
yield mock
|
||||
|
||||
|
||||
@pytest.fixture(name="empty_attribution_response")
|
||||
def empty_attribution_response_fixture():
|
||||
"""Return valid api response with an empty attribution."""
|
||||
@pytest.fixture(name="no_attribution_response")
|
||||
def no_attribution_response_fixture():
|
||||
"""Return valid api response without attribution."""
|
||||
with patch(
|
||||
"herepy.RoutingApi.public_transport_timetable",
|
||||
return_value=EMPTY_ATTRIBUTION_RESPONSE,
|
||||
"here_transit.HERETransitApi.route",
|
||||
return_value=NO_ATTRIBUTION_TRANSIT_RESPONSE,
|
||||
), patch(
|
||||
"here_routing.HERERoutingApi.route",
|
||||
return_value=RESPONSE,
|
||||
) as mock:
|
||||
yield mock
|
||||
|
|
|
@ -1,8 +1,27 @@
|
|||
"""Constants for HERE Travel Time tests."""
|
||||
|
||||
from homeassistant.components.here_travel_time.const import (
|
||||
CONF_DESTINATION_LATITUDE,
|
||||
CONF_DESTINATION_LONGITUDE,
|
||||
CONF_ORIGIN_LATITUDE,
|
||||
CONF_ORIGIN_LONGITUDE,
|
||||
TRAVEL_MODE_CAR,
|
||||
)
|
||||
from homeassistant.const import CONF_API_KEY, CONF_MODE, CONF_NAME
|
||||
|
||||
API_KEY = "test"
|
||||
|
||||
CAR_ORIGIN_LATITUDE = "38.9"
|
||||
CAR_ORIGIN_LONGITUDE = "-77.04833"
|
||||
CAR_DESTINATION_LATITUDE = "39.0"
|
||||
CAR_DESTINATION_LONGITUDE = "-77.1"
|
||||
ORIGIN_LATITUDE = "38.9"
|
||||
ORIGIN_LONGITUDE = "-77.04833"
|
||||
DESTINATION_LATITUDE = "39.0"
|
||||
DESTINATION_LONGITUDE = "-77.1"
|
||||
|
||||
DEFAULT_CONFIG = {
|
||||
CONF_ORIGIN_LATITUDE: float(ORIGIN_LATITUDE),
|
||||
CONF_ORIGIN_LONGITUDE: float(ORIGIN_LONGITUDE),
|
||||
CONF_DESTINATION_LATITUDE: float(DESTINATION_LATITUDE),
|
||||
CONF_DESTINATION_LONGITUDE: float(DESTINATION_LONGITUDE),
|
||||
CONF_API_KEY: API_KEY,
|
||||
CONF_MODE: TRAVEL_MODE_CAR,
|
||||
CONF_NAME: "test",
|
||||
}
|
||||
|
|
|
@ -1,304 +1,208 @@
|
|||
{
|
||||
"response": {
|
||||
"metaInfo": {
|
||||
"timestamp": "2019-07-19T07:38:39Z",
|
||||
"mapVersion": "8.30.98.154",
|
||||
"moduleVersion": "7.2.201928-4446",
|
||||
"interfaceVersion": "2.6.64",
|
||||
"availableMapVersion": ["8.30.98.154"]
|
||||
},
|
||||
"route": [
|
||||
{
|
||||
"waypoint": [
|
||||
{
|
||||
"linkId": "+732182239",
|
||||
"mappedPosition": {
|
||||
"latitude": 38.9,
|
||||
"longitude": -77.0488358
|
||||
},
|
||||
"originalPosition": {
|
||||
"latitude": 38.9,
|
||||
"longitude": -77.0483301
|
||||
},
|
||||
"type": "stopOver",
|
||||
"spot": 0.4946237,
|
||||
"sideOfStreet": "right",
|
||||
"mappedRoadName": "22nd St NW",
|
||||
"label": "22nd St NW",
|
||||
"shapeIndex": 0,
|
||||
"source": "user"
|
||||
},
|
||||
{
|
||||
"linkId": "+942865877",
|
||||
"mappedPosition": {
|
||||
"latitude": 38.9999735,
|
||||
"longitude": -77.100141
|
||||
},
|
||||
"originalPosition": {
|
||||
"latitude": 38.9999999,
|
||||
"longitude": -77.1000001
|
||||
},
|
||||
"type": "stopOver",
|
||||
"spot": 1,
|
||||
"sideOfStreet": "left",
|
||||
"mappedRoadName": "Service Rd S",
|
||||
"label": "Service Rd S",
|
||||
"shapeIndex": 279,
|
||||
"source": "user"
|
||||
}
|
||||
],
|
||||
"mode": {
|
||||
"type": "fastest",
|
||||
"transportModes": ["car"],
|
||||
"trafficMode": "enabled",
|
||||
"feature": []
|
||||
},
|
||||
"leg": [
|
||||
{
|
||||
"start": {
|
||||
"linkId": "+732182239",
|
||||
"mappedPosition": {
|
||||
"latitude": 38.9,
|
||||
"longitude": -77.0488358
|
||||
},
|
||||
"originalPosition": {
|
||||
"latitude": 38.9,
|
||||
"longitude": -77.0483301
|
||||
},
|
||||
"type": "stopOver",
|
||||
"spot": 0.4946237,
|
||||
"sideOfStreet": "right",
|
||||
"mappedRoadName": "22nd St NW",
|
||||
"label": "22nd St NW",
|
||||
"shapeIndex": 0,
|
||||
"source": "user"
|
||||
},
|
||||
"end": {
|
||||
"linkId": "+942865877",
|
||||
"mappedPosition": {
|
||||
"latitude": 38.9999735,
|
||||
"longitude": -77.100141
|
||||
},
|
||||
"originalPosition": {
|
||||
"latitude": 38.9999999,
|
||||
"longitude": -77.1000001
|
||||
},
|
||||
"type": "stopOver",
|
||||
"spot": 1,
|
||||
"sideOfStreet": "left",
|
||||
"mappedRoadName": "Service Rd S",
|
||||
"label": "Service Rd S",
|
||||
"shapeIndex": 279,
|
||||
"source": "user"
|
||||
},
|
||||
"length": 23903,
|
||||
"travelTime": 1884,
|
||||
"maneuver": [
|
||||
{
|
||||
"position": {
|
||||
"latitude": 38.9,
|
||||
"longitude": -77.0488358
|
||||
},
|
||||
"instruction": "Head toward <span class=\"toward_street\">I St NW</span> on <span class=\"street\">22nd St NW</span>. <span class=\"distance-description\">Go for <span class=\"length\">279 m</span>.</span>",
|
||||
"travelTime": 95,
|
||||
"length": 279,
|
||||
"id": "M1",
|
||||
"_type": "PrivateTransportManeuverType"
|
||||
},
|
||||
{
|
||||
"position": {
|
||||
"latitude": 38.9021051,
|
||||
"longitude": -77.048825
|
||||
},
|
||||
"instruction": "Turn <span class=\"direction\">left</span> toward <span class=\"sign\">Pennsylvania Ave NW</span>. <span class=\"distance-description\">Go for <span class=\"length\">71 m</span>.</span>",
|
||||
"travelTime": 21,
|
||||
"length": 71,
|
||||
"id": "M2",
|
||||
"_type": "PrivateTransportManeuverType"
|
||||
},
|
||||
{
|
||||
"position": {
|
||||
"latitude": 38.902545,
|
||||
"longitude": -77.0494151
|
||||
},
|
||||
"instruction": "Take the <span class=\"exit\">3rd exit</span> from Washington Cir NW roundabout onto <span class=\"next-street\">K St NW</span>. <span class=\"distance-description\">Go for <span class=\"length\">352 m</span>.</span>",
|
||||
"travelTime": 90,
|
||||
"length": 352,
|
||||
"id": "M3",
|
||||
"_type": "PrivateTransportManeuverType"
|
||||
},
|
||||
{
|
||||
"position": {
|
||||
"latitude": 38.9026523,
|
||||
"longitude": -77.0529449
|
||||
},
|
||||
"instruction": "Keep <span class=\"direction\">left</span> onto <span class=\"next-street\">K St NW</span> <span class=\"number\">(US-29)</span>. <span class=\"distance-description\">Go for <span class=\"length\">201 m</span>.</span>",
|
||||
"travelTime": 30,
|
||||
"length": 201,
|
||||
"id": "M4",
|
||||
"_type": "PrivateTransportManeuverType"
|
||||
},
|
||||
{
|
||||
"position": {
|
||||
"latitude": 38.9025235,
|
||||
"longitude": -77.0552516
|
||||
},
|
||||
"instruction": "Keep <span class=\"direction\">right</span> onto <span class=\"next-street\">Whitehurst Fwy</span> <span class=\"number\">(US-29)</span>. <span class=\"distance-description\">Go for <span class=\"length\">1.4 km</span>.</span>",
|
||||
"travelTime": 131,
|
||||
"length": 1381,
|
||||
"id": "M5",
|
||||
"_type": "PrivateTransportManeuverType"
|
||||
},
|
||||
{
|
||||
"position": {
|
||||
"latitude": 38.9050448,
|
||||
"longitude": -77.0701969
|
||||
},
|
||||
"instruction": "Turn <span class=\"direction\">left</span> onto <span class=\"next-street\">M St NW</span>. <span class=\"distance-description\">Go for <span class=\"length\">784 m</span>.</span>",
|
||||
"travelTime": 78,
|
||||
"length": 784,
|
||||
"id": "M6",
|
||||
"_type": "PrivateTransportManeuverType"
|
||||
},
|
||||
{
|
||||
"position": {
|
||||
"latitude": 38.9060318,
|
||||
"longitude": -77.0790696
|
||||
},
|
||||
"instruction": "Turn <span class=\"direction\">slightly left</span> onto <span class=\"next-street\">Canal Rd NW</span>. <span class=\"distance-description\">Go for <span class=\"length\">4.2 km</span>.</span>",
|
||||
"travelTime": 277,
|
||||
"length": 4230,
|
||||
"id": "M7",
|
||||
"_type": "PrivateTransportManeuverType"
|
||||
},
|
||||
{
|
||||
"position": {
|
||||
"latitude": 38.9303219,
|
||||
"longitude": -77.1117926
|
||||
},
|
||||
"instruction": "Continue on <span class=\"next-street\">Clara Barton Pkwy</span>. <span class=\"distance-description\">Go for <span class=\"length\">844 m</span>.</span>",
|
||||
"travelTime": 55,
|
||||
"length": 844,
|
||||
"id": "M8",
|
||||
"_type": "PrivateTransportManeuverType"
|
||||
},
|
||||
{
|
||||
"position": {
|
||||
"latitude": 38.9368558,
|
||||
"longitude": -77.1166742
|
||||
},
|
||||
"instruction": "Continue on <span class=\"next-street\">Clara Barton Pkwy</span>. <span class=\"distance-description\">Go for <span class=\"length\">4.7 km</span>.</span>",
|
||||
"travelTime": 298,
|
||||
"length": 4652,
|
||||
"id": "M9",
|
||||
"_type": "PrivateTransportManeuverType"
|
||||
},
|
||||
{
|
||||
"position": {
|
||||
"latitude": 38.9706838,
|
||||
"longitude": -77.1461463
|
||||
},
|
||||
"instruction": "Keep <span class=\"direction\">right</span> onto <span class=\"next-street\">Cabin John Pkwy N</span> toward <span class=\"sign\"><span lang=\"en\">I-495 N</span></span>. <span class=\"distance-description\">Go for <span class=\"length\">2.1 km</span>.</span>",
|
||||
"travelTime": 91,
|
||||
"length": 2069,
|
||||
"id": "M10",
|
||||
"_type": "PrivateTransportManeuverType"
|
||||
},
|
||||
{
|
||||
"position": {
|
||||
"latitude": 38.9858222,
|
||||
"longitude": -77.1571326
|
||||
},
|
||||
"instruction": "Take <span class=\"direction\">left</span> ramp onto <span class=\"number\">I-495 N</span> <span class=\"next-street\">(Capital Beltway)</span>. <span class=\"distance-description\">Go for <span class=\"length\">5.5 km</span>.</span>",
|
||||
"travelTime": 238,
|
||||
"length": 5538,
|
||||
"id": "M11",
|
||||
"_type": "PrivateTransportManeuverType"
|
||||
},
|
||||
{
|
||||
"position": {
|
||||
"latitude": 39.0153587,
|
||||
"longitude": -77.1221781
|
||||
},
|
||||
"instruction": "Take exit <span class=\"exit\">36</span> toward <span class=\"sign\"><span lang=\"en\">Bethesda</span></span> onto <span class=\"number\">MD-187 S</span> <span class=\"next-street\">(Old Georgetown Rd)</span>. <span class=\"distance-description\">Go for <span class=\"length\">2.4 km</span>.</span>",
|
||||
"travelTime": 211,
|
||||
"length": 2365,
|
||||
"id": "M12",
|
||||
"_type": "PrivateTransportManeuverType"
|
||||
},
|
||||
{
|
||||
"position": {
|
||||
"latitude": 38.9981818,
|
||||
"longitude": -77.1093571
|
||||
},
|
||||
"instruction": "Turn <span class=\"direction\">left</span> onto <span class=\"next-street\">Lincoln Dr</span>. <span class=\"distance-description\">Go for <span class=\"length\">506 m</span>.</span>",
|
||||
"travelTime": 127,
|
||||
"length": 506,
|
||||
"id": "M13",
|
||||
"_type": "PrivateTransportManeuverType"
|
||||
},
|
||||
{
|
||||
"position": {
|
||||
"latitude": 38.9987397,
|
||||
"longitude": -77.1037138
|
||||
},
|
||||
"instruction": "Turn <span class=\"direction\">right</span> onto <span class=\"next-street\">Service Rd W</span>. <span class=\"distance-description\">Go for <span class=\"length\">121 m</span>.</span>",
|
||||
"travelTime": 36,
|
||||
"length": 121,
|
||||
"id": "M14",
|
||||
"_type": "PrivateTransportManeuverType"
|
||||
},
|
||||
{
|
||||
"position": {
|
||||
"latitude": 38.9976454,
|
||||
"longitude": -77.1036172
|
||||
},
|
||||
"instruction": "Turn <span class=\"direction\">left</span> onto <span class=\"next-street\">Service Rd S</span>. <span class=\"distance-description\">Go for <span class=\"length\">510 m</span>.</span>",
|
||||
"travelTime": 106,
|
||||
"length": 510,
|
||||
"id": "M15",
|
||||
"_type": "PrivateTransportManeuverType"
|
||||
},
|
||||
{
|
||||
"position": {
|
||||
"latitude": 38.9999735,
|
||||
"longitude": -77.100141
|
||||
},
|
||||
"instruction": "Arrive at <span class=\"street\">Service Rd S</span>. Your destination is on the left.",
|
||||
"travelTime": 0,
|
||||
"length": 0,
|
||||
"id": "M16",
|
||||
"_type": "PrivateTransportManeuverType"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"summary": {
|
||||
"distance": 23903,
|
||||
"trafficTime": 1861,
|
||||
"baseTime": 1803,
|
||||
"flags": [
|
||||
"noThroughRoad",
|
||||
"motorway",
|
||||
"builtUpArea",
|
||||
"park",
|
||||
"privateRoad"
|
||||
],
|
||||
"text": "The trip takes <span class=\"length\">23.9 km</span> and <span class=\"time\">31 mins</span>.",
|
||||
"travelTime": 1861,
|
||||
"_type": "RouteSummaryType"
|
||||
}
|
||||
}
|
||||
],
|
||||
"language": "en-us",
|
||||
"sourceAttribution": {
|
||||
"attribution": "With the support of <span class=\"company\"><a href=\"https://transit.api.here.com/r?appId=Mt1bOYh3m9uxE7r3wuUx&u=https://wego.here.com\">HERE Technologies</a></span>. All information is provided without warranty of any kind.",
|
||||
"supplier": [
|
||||
"routes": [
|
||||
{
|
||||
"id": "bace556c-2a16-4d93-a6c0-f595bbc89aa6",
|
||||
"sections": [
|
||||
{
|
||||
"title": "HERE Technologies",
|
||||
"href": "https://transit.api.here.com/r?appId=Mt1bOYh3m9uxE7r3wuUx&u=https://wego.here.com"
|
||||
"id": "3ef521f3-7f88-480b-9b84-567a352c323c",
|
||||
"type": "vehicle",
|
||||
"departure": {
|
||||
"time": "2022-10-24T05:53:00-04:00",
|
||||
"place": {
|
||||
"type": "place",
|
||||
"location": {
|
||||
"lat": 38.8999937,
|
||||
"lng": -77.0479682
|
||||
},
|
||||
"originalLocation": {
|
||||
"lat": 38.9,
|
||||
"lng": -77.0483301
|
||||
}
|
||||
}
|
||||
},
|
||||
"arrival": {
|
||||
"time": "2022-10-24T06:22:36-04:00",
|
||||
"place": {
|
||||
"type": "place",
|
||||
"location": {
|
||||
"lat": 38.99997,
|
||||
"lng": -77.10014
|
||||
},
|
||||
"originalLocation": {
|
||||
"lat": 38.9999999,
|
||||
"lng": -77.1000001
|
||||
}
|
||||
}
|
||||
},
|
||||
"summary": {
|
||||
"duration": 1776,
|
||||
"length": 13682,
|
||||
"baseDuration": 1571
|
||||
},
|
||||
"polyline": "BG0xomqC_p0-yE7ZXA_dA3XArnBTnpBkXAsYUkXAo4BTgZAA8GoB0F8BoG4DwHkDsEgFsEsEwC4D8BwCoB8BUwCU4DU0FT4DTsE7BkD7BwC7BgFzFwCrEkNwMkNwMwW8VgKsJoLAgPA4cUgeAwqBA0PAwHA8GA0yBAwbU4NA0FAgPAsTAoBAsnBAsEAkXAwvBU8VoBwMoBwMjD4NUkIUkD3IsEvMsEnLkI7V8GzUsE7LkD3IsEUoBAwCAoBAwCT8BToBTwC7BoBnB8BvCoB7BoBvCoBjDUvCA_EwCnLwC3IwCvHwC3I4D_E4DrE4D_EwCjD8BvCwCjDwCjDwC3DgFvHgFvH0KnQwRrY4XvgBkInLkDrEwCjDwHzK8GrJ0FjI0KrOgKjNwHzKsJjN8QjXkNjSwlBnzBoazjBwbnkB4D_EgenpBwCjDgFnGgF7G4cjmBoB7B4DnGwRzZkSrY4S_YgK3NoG3I0K_O4DzFoBnB4XvgB0K_OopBz3BoQrT4IjIoGrEkN_E8ajIkNrEoV_J0PrJsJvHwH7G4N3N0K3NgKjNkI7LsE7GwCrEoG_O4D_JsEvM0FnV0FjcoBvW8B3NwC_JwHvWUvCsJ7QokBnzBsTnasOrTwM7QsJjNoB7BoB7BsYvgB4I7LkNjSsTnawRrYgPnV4hB_sBgK3NoGrJwCjD0ZkDgZsEsEUkNwC4DU0UwCwlB0F0K8Bk_B0K4hBoG4I8B0FUgZkD0UwC8GUsYAkSAkSnB0KvC4rBjSkNzF0PvHwR3IoBToQjI0UnLwRrJwWvMwCnBozB3c4rB3XsT_JoVzKgjB_ToQrJsJzF8G3D0Z_OoL7GkrB3XoV7L0jBrTkIrE0P3IwCnBwWvM8VvMoBTwW7LkIrE4I_E4NvHoGjDsTnLwHrEkI_E4D7BoBT0FjDwMvHoBT4IrE4D7BsdzP0FjDgKzFoL7G4I_E8BnBoa_O4X3NoQ3IsTzK8BnBsJ_EwCnBgKrEwHjDsJvCwH7B8anG8QjD0PrE0ZnGoajI4D7B8zBvbgKrE8BnBwWnLwR3I0K_EoQ3I4I_EoLnGoBT4IrEgK_EofzP4X7L0UzKsxBzZkIrE4IrEoVzKsTrJ8QjIoGjDwH3DkcrOwWnLwR3I0UzKsYvMoajN08BnfwW7LwRjD0PnG0PnGwHjD0P7GgjBvRoVnLoVnL0PjI8VnLkmB3SoVzK8BnB4S3IoajN0ejN4SjIwHvCkS7GoBTwR_E8QrEoV_Ek6B7L8GnBoa3DwgB3D84B_J8zB7G03B_JsnBnGoQvCsYrEwHnB08B_Jo4BrJoVrE8L3D8GvC8QnG4NnGoQ7GkSjIsT_JkSrJ0UzK0Z3NgFvCwMnGoLzF0U_J4D7BwWnL4SrJ4D7BkmB3SwMnGoLzFkDnBkIrE0FvCof_O8ajN8V_JgK_EgZnL4N7G8QjI0Z3IkNrE8BToGjD0F3DwHzFsJvCwgBjIgU_EwMjD4DT4DTgFnBkhB3IoLjDoBAwqBrJ4DTwqB3IwHnBsTrEwH7BkS3DkI7BwCT4X_E8BToBA4I7BgKvCoQjDsY_E8azF0PjD8LjDwMvCgUzF0enGwR3DkI7B0UrEgUrEwR3D0PjDoQjD8L7BoVjD0P7B8GTsO7BgejD8Q7BoQ7BwMnBsJnBwHTgejDkSnBgPnBkNT0UnBoBAwHTwHT0UvC4N7B0KT4DT8BAT3DA_ET_d8BrOwC_J8GnQgPjXsEzFsJ7QsJ3XwHvWU_OoBvWAnGTzP_iBjSzK3D_JnB7LAvW8B7LUv5BsEoGgPoGsJ0FoGsEwC4D8BkDU8VToGA4SoGwHnBsJ3DoQA4DT8L7BgFkD8BwCUsE",
|
||||
"spans": [
|
||||
{
|
||||
"offset": 0,
|
||||
"names": [
|
||||
{
|
||||
"value": "22nd St NW",
|
||||
"language": "en"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"offset": 1,
|
||||
"names": [
|
||||
{
|
||||
"value": "H St NW",
|
||||
"language": "en"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"offset": 5,
|
||||
"names": [
|
||||
{
|
||||
"value": "23rd St NW",
|
||||
"language": "en"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"offset": 10,
|
||||
"names": [
|
||||
{
|
||||
"value": "Washington Cir NW",
|
||||
"language": "en"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"offset": 29,
|
||||
"names": [
|
||||
{
|
||||
"value": "New Hampshire Ave NW",
|
||||
"language": "en"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"offset": 33,
|
||||
"names": [
|
||||
{
|
||||
"value": "22nd St NW",
|
||||
"language": "en"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"offset": 57,
|
||||
"names": [
|
||||
{
|
||||
"value": "Massachusetts Ave NW",
|
||||
"language": "en"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"offset": 64,
|
||||
"names": [
|
||||
{
|
||||
"value": "Massachusetts Ave NW",
|
||||
"language": "en"
|
||||
},
|
||||
{
|
||||
"value": "Sheridan Cir NW",
|
||||
"language": "en"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"offset": 78,
|
||||
"names": [
|
||||
{
|
||||
"value": "Sheridan Cir NW",
|
||||
"language": "en"
|
||||
},
|
||||
{
|
||||
"value": "Massachusetts Ave NW",
|
||||
"language": "en"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"offset": 79,
|
||||
"names": [
|
||||
{
|
||||
"value": "Massachusetts Ave NW",
|
||||
"language": "en"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"offset": 174,
|
||||
"names": [
|
||||
{
|
||||
"value": "Wisconsin Ave NW",
|
||||
"language": "en"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"offset": 291,
|
||||
"names": [
|
||||
{
|
||||
"value": "Wisconsin Ave",
|
||||
"language": "en"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"offset": 408,
|
||||
"names": [
|
||||
{
|
||||
"value": "Rockville Pike",
|
||||
"language": "en"
|
||||
},
|
||||
{
|
||||
"value": "Wisconsin Ave",
|
||||
"language": "en"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"offset": 428,
|
||||
"names": [
|
||||
{
|
||||
"value": "South Dr",
|
||||
"language": "en"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"offset": 443,
|
||||
"names": [
|
||||
{
|
||||
"value": "Center Dr",
|
||||
"language": "en"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"offset": 450,
|
||||
"names": [
|
||||
{
|
||||
"value": "Service Rd S",
|
||||
"language": "en"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"transport": {
|
||||
"mode": "car"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -1,131 +0,0 @@
|
|||
{
|
||||
"response": {
|
||||
"metaInfo": {
|
||||
"timestamp": "2019-07-19T07:38:39Z",
|
||||
"mapVersion": "8.30.98.154",
|
||||
"moduleVersion": "7.2.201928-4446",
|
||||
"interfaceVersion": "2.6.64",
|
||||
"availableMapVersion": ["8.30.98.154"]
|
||||
},
|
||||
"route": [
|
||||
{
|
||||
"waypoint": [
|
||||
{
|
||||
"linkId": "+732182239",
|
||||
"mappedPosition": {
|
||||
"latitude": 38.9,
|
||||
"longitude": -77.0488358
|
||||
},
|
||||
"originalPosition": {
|
||||
"latitude": 38.9,
|
||||
"longitude": -77.0483301
|
||||
},
|
||||
"type": "stopOver",
|
||||
"spot": 0.4946237,
|
||||
"sideOfStreet": "right",
|
||||
"mappedRoadName": "22nd St NW",
|
||||
"label": "22nd St NW",
|
||||
"shapeIndex": 0,
|
||||
"source": "user"
|
||||
},
|
||||
{
|
||||
"linkId": "+942865877",
|
||||
"mappedPosition": {
|
||||
"latitude": 38.9999735,
|
||||
"longitude": -77.100141
|
||||
},
|
||||
"originalPosition": {
|
||||
"latitude": 38.9999999,
|
||||
"longitude": -77.1000001
|
||||
},
|
||||
"type": "stopOver",
|
||||
"spot": 1,
|
||||
"sideOfStreet": "left",
|
||||
"mappedRoadName": "Service Rd S",
|
||||
"label": "Service Rd S",
|
||||
"shapeIndex": 279,
|
||||
"source": "user"
|
||||
}
|
||||
],
|
||||
"mode": {
|
||||
"type": "fastest",
|
||||
"transportModes": ["car"],
|
||||
"trafficMode": "enabled",
|
||||
"feature": []
|
||||
},
|
||||
"leg": [
|
||||
{
|
||||
"start": {
|
||||
"linkId": "+732182239",
|
||||
"mappedPosition": {
|
||||
"latitude": 38.9,
|
||||
"longitude": -77.0488358
|
||||
},
|
||||
"originalPosition": {
|
||||
"latitude": 38.9,
|
||||
"longitude": -77.0483301
|
||||
},
|
||||
"type": "stopOver",
|
||||
"spot": 0.4946237,
|
||||
"sideOfStreet": "right",
|
||||
"mappedRoadName": "22nd St NW",
|
||||
"label": "22nd St NW",
|
||||
"shapeIndex": 0,
|
||||
"source": "user"
|
||||
},
|
||||
"end": {
|
||||
"linkId": "+942865877",
|
||||
"mappedPosition": {
|
||||
"latitude": 38.9999735,
|
||||
"longitude": -77.100141
|
||||
},
|
||||
"originalPosition": {
|
||||
"latitude": 38.9999999,
|
||||
"longitude": -77.1000001
|
||||
},
|
||||
"type": "stopOver",
|
||||
"spot": 1,
|
||||
"sideOfStreet": "left",
|
||||
"mappedRoadName": "Service Rd S",
|
||||
"label": "Service Rd S",
|
||||
"shapeIndex": 279,
|
||||
"source": "user"
|
||||
},
|
||||
"length": 23903,
|
||||
"travelTime": 1884,
|
||||
"maneuver": [
|
||||
{
|
||||
"position": {
|
||||
"latitude": 38.9999735,
|
||||
"longitude": -77.100141
|
||||
},
|
||||
"instruction": "Arrive at <span class=\"street\">Service Rd S</span>. Your destination is on the left.",
|
||||
"travelTime": 0,
|
||||
"length": 0,
|
||||
"id": "M16",
|
||||
"_type": "PrivateTransportManeuverType"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"summary": {
|
||||
"distance": 23903,
|
||||
"trafficTime": 1861,
|
||||
"baseTime": 1803,
|
||||
"flags": [
|
||||
"noThroughRoad",
|
||||
"motorway",
|
||||
"builtUpArea",
|
||||
"park",
|
||||
"privateRoad"
|
||||
],
|
||||
"text": "The trip takes <span class=\"length\">23.9 km</span> and <span class=\"time\">31 mins</span>.",
|
||||
"travelTime": 1861,
|
||||
"_type": "RouteSummaryType"
|
||||
}
|
||||
}
|
||||
],
|
||||
"language": "en-us",
|
||||
"sourceAttribution": {}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,145 @@
|
|||
{
|
||||
"routes": [
|
||||
{
|
||||
"id": "C0",
|
||||
"sections": [
|
||||
{
|
||||
"id": "C0-S0",
|
||||
"type": "pedestrian",
|
||||
"actions": [
|
||||
{
|
||||
"action": "depart",
|
||||
"duration": 1111,
|
||||
"instruction": "Head west on Wilhelm-Fay-Straße. Go for 1.1 km.",
|
||||
"length": 1099,
|
||||
"offset": 0
|
||||
},
|
||||
{
|
||||
"action": "roundaboutExit",
|
||||
"duration": 73,
|
||||
"instruction": "Walk right around the roundabout and turn at the 1st street Frankfurter Straße. Go for 63 m.",
|
||||
"length": 63,
|
||||
"offset": 40,
|
||||
"exit": 1,
|
||||
"direction": "right"
|
||||
},
|
||||
{
|
||||
"action": "arrive",
|
||||
"duration": 0,
|
||||
"instruction": "Arrive at Frankfurter Straße. Your destination is on the left.",
|
||||
"length": 0,
|
||||
"offset": 47
|
||||
}
|
||||
],
|
||||
"travelSummary": {
|
||||
"duration": 1140,
|
||||
"length": 1162
|
||||
},
|
||||
"departure": {
|
||||
"time": "2022-07-19T15:39:00+02:00",
|
||||
"place": {
|
||||
"type": "place",
|
||||
"location": {
|
||||
"lat": 50.127787,
|
||||
"lng": 8.582082
|
||||
}
|
||||
}
|
||||
},
|
||||
"arrival": {
|
||||
"time": "2022-07-19T15:58:00+02:00",
|
||||
"place": {
|
||||
"name": "Eschborn Alfred-Herrhausen-Allee",
|
||||
"type": "station",
|
||||
"location": {
|
||||
"lat": 50.135176,
|
||||
"lng": 8.572745
|
||||
},
|
||||
"id": "110439568"
|
||||
}
|
||||
},
|
||||
"polyline": "BGwhxz_Cgv5rQwDrQ8BvHkNr2BsE7LkI7V0FrO4I7QgF3IoGzKkSjXsTzU0FzFkXnV0tB7pBkN3N0FzF4DjD0KrJwW3S0UrJwWnG4cjI0FjDkDvC8BnB0F_EsErEkDrEkIrJ8G_J4IrOsJ_O8VzjB8Q7asJnQwbztB8G7L0FrJsE7GkNvW8BwC4D8BsEnB8BjDgFkIkD0FqHwL",
|
||||
"transport": {
|
||||
"mode": "pedestrian"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "C0-S3",
|
||||
"type": "pedestrian",
|
||||
"actions": [
|
||||
{
|
||||
"action": "depart",
|
||||
"duration": 166,
|
||||
"instruction": "Head southwest on Hunsrückstraße. Go for 155 m.",
|
||||
"length": 155,
|
||||
"offset": 0
|
||||
},
|
||||
{
|
||||
"action": "turn",
|
||||
"duration": 91,
|
||||
"instruction": "Turn right onto Stolberger Straße. Go for 82 m.",
|
||||
"length": 82,
|
||||
"offset": 4,
|
||||
"direction": "right",
|
||||
"severity": "quite"
|
||||
},
|
||||
{
|
||||
"action": "turn",
|
||||
"duration": 476,
|
||||
"instruction": "Turn left onto Horchheimer Straße. Go for 466 m.",
|
||||
"length": 466,
|
||||
"offset": 9,
|
||||
"direction": "left",
|
||||
"severity": "quite"
|
||||
},
|
||||
{
|
||||
"action": "turn",
|
||||
"duration": 18,
|
||||
"instruction": "Turn left onto Hessenring. Go for 18 m.",
|
||||
"length": 18,
|
||||
"offset": 21,
|
||||
"direction": "left",
|
||||
"severity": "quite"
|
||||
},
|
||||
{
|
||||
"action": "arrive",
|
||||
"duration": 0,
|
||||
"instruction": "Arrive at Hessenring. Your destination is on the left.",
|
||||
"length": 0,
|
||||
"offset": 22
|
||||
}
|
||||
],
|
||||
"travelSummary": {
|
||||
"duration": 720,
|
||||
"length": 721
|
||||
},
|
||||
"departure": {
|
||||
"time": "2022-07-19T17:15:00+02:00",
|
||||
"place": {
|
||||
"name": "Wiesbaden-Nordenstadt Stolberger Straße",
|
||||
"type": "station",
|
||||
"location": {
|
||||
"lat": 50.060615,
|
||||
"lng": 8.344163
|
||||
},
|
||||
"id": "110812533"
|
||||
}
|
||||
},
|
||||
"arrival": {
|
||||
"time": "2022-07-19T17:27:00+02:00",
|
||||
"place": {
|
||||
"type": "place",
|
||||
"location": {
|
||||
"lat": 50.060941,
|
||||
"lng": 8.336477
|
||||
}
|
||||
}
|
||||
},
|
||||
"polyline": "BG20uv_C4lp9P9nBn-B3DzFvC3DnQjX8GvHkDvC4DnBoGoBkX8L0F3cwCnLkN7iCnBzhCAnG8B3wBsEzZkD_OwCzP8GnkBwHjmB8GvlBhJ1G",
|
||||
"transport": {
|
||||
"mode": "pedestrian"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,153 @@
|
|||
{
|
||||
"routes": [
|
||||
{
|
||||
"id": "C0",
|
||||
"sections": [
|
||||
{
|
||||
"id": "C0-S0",
|
||||
"type": "pedestrian",
|
||||
"actions": [
|
||||
{
|
||||
"action": "depart",
|
||||
"duration": 1111,
|
||||
"instruction": "Head west on Wilhelm-Fay-Straße. Go for 1.1 km.",
|
||||
"length": 1099,
|
||||
"offset": 0
|
||||
},
|
||||
{
|
||||
"action": "roundaboutExit",
|
||||
"duration": 73,
|
||||
"instruction": "Walk right around the roundabout and turn at the 1st street Frankfurter Straße. Go for 63 m.",
|
||||
"length": 63,
|
||||
"offset": 40,
|
||||
"exit": 1,
|
||||
"direction": "right"
|
||||
},
|
||||
{
|
||||
"action": "arrive",
|
||||
"duration": 0,
|
||||
"instruction": "Arrive at Frankfurter Straße. Your destination is on the left.",
|
||||
"length": 0,
|
||||
"offset": 47
|
||||
}
|
||||
],
|
||||
"travelSummary": {
|
||||
"duration": 1140,
|
||||
"length": 1162
|
||||
},
|
||||
"departure": {
|
||||
"time": "2022-07-19T15:39:00+02:00",
|
||||
"place": {
|
||||
"type": "place",
|
||||
"location": {
|
||||
"lat": 50.127787,
|
||||
"lng": 8.582082
|
||||
}
|
||||
}
|
||||
},
|
||||
"arrival": {
|
||||
"time": "2022-07-19T15:58:00+02:00",
|
||||
"place": {
|
||||
"name": "Eschborn Alfred-Herrhausen-Allee",
|
||||
"type": "station",
|
||||
"location": {
|
||||
"lat": 50.135176,
|
||||
"lng": 8.572745
|
||||
},
|
||||
"id": "110439568"
|
||||
}
|
||||
},
|
||||
"polyline": "BGwhxz_Cgv5rQwDrQ8BvHkNr2BsE7LkI7V0FrO4I7QgF3IoGzKkSjXsTzU0FzFkXnV0tB7pBkN3N0FzF4DjD0KrJwW3S0UrJwWnG4cjI0FjDkDvC8BnB0F_EsErEkDrEkIrJ8G_J4IrOsJ_O8VzjB8Q7asJnQwbztB8G7L0FrJsE7GkNvW8BwC4D8BsEnB8BjDgFkIkD0FqHwL",
|
||||
"transport": {
|
||||
"mode": "pedestrian"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "C0-S3",
|
||||
"type": "pedestrian",
|
||||
"actions": [
|
||||
{
|
||||
"action": "depart",
|
||||
"duration": 166,
|
||||
"instruction": "Head southwest on Hunsrückstraße. Go for 155 m.",
|
||||
"length": 155,
|
||||
"offset": 0
|
||||
},
|
||||
{
|
||||
"action": "turn",
|
||||
"duration": 91,
|
||||
"instruction": "Turn right onto Stolberger Straße. Go for 82 m.",
|
||||
"length": 82,
|
||||
"offset": 4,
|
||||
"direction": "right",
|
||||
"severity": "quite"
|
||||
},
|
||||
{
|
||||
"action": "turn",
|
||||
"duration": 476,
|
||||
"instruction": "Turn left onto Horchheimer Straße. Go for 466 m.",
|
||||
"length": 466,
|
||||
"offset": 9,
|
||||
"direction": "left",
|
||||
"severity": "quite"
|
||||
},
|
||||
{
|
||||
"action": "turn",
|
||||
"duration": 18,
|
||||
"instruction": "Turn left onto Hessenring. Go for 18 m.",
|
||||
"length": 18,
|
||||
"offset": 21,
|
||||
"direction": "left",
|
||||
"severity": "quite"
|
||||
},
|
||||
{
|
||||
"action": "arrive",
|
||||
"duration": 0,
|
||||
"instruction": "Arrive at Hessenring. Your destination is on the left.",
|
||||
"length": 0,
|
||||
"offset": 22
|
||||
}
|
||||
],
|
||||
"travelSummary": {
|
||||
"duration": 720,
|
||||
"length": 721
|
||||
},
|
||||
"departure": {
|
||||
"time": "2022-07-19T17:15:00+02:00",
|
||||
"place": {
|
||||
"name": "Wiesbaden-Nordenstadt Stolberger Straße",
|
||||
"type": "station",
|
||||
"location": {
|
||||
"lat": 50.060615,
|
||||
"lng": 8.344163
|
||||
},
|
||||
"id": "110812533"
|
||||
}
|
||||
},
|
||||
"arrival": {
|
||||
"time": "2022-07-19T17:27:00+02:00",
|
||||
"place": {
|
||||
"type": "place",
|
||||
"location": {
|
||||
"lat": 50.060941,
|
||||
"lng": 8.336477
|
||||
}
|
||||
}
|
||||
},
|
||||
"polyline": "BG20uv_C4lp9P9nBn-B3DzFvC3DnQjX8GvHkDvC4DnBoGoBkX8L0F3cwCnLkN7iCnBzhCAnG8B3wBsEzZkD_OwCzP8GnkBwHjmB8GvlBhJ1G",
|
||||
"transport": {
|
||||
"mode": "pedestrian"
|
||||
},
|
||||
"attributions": [
|
||||
{
|
||||
"id": "R00370b-C0-S2-link-0",
|
||||
"href": "http://creativecommons.org/licenses/by/3.0/it/",
|
||||
"text": "Some line names used in this product or service were edited to align with official transportation maps.",
|
||||
"type": "disclaimer"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,8 +1,7 @@
|
|||
"""Test the HERE Travel Time config flow."""
|
||||
from unittest.mock import patch
|
||||
|
||||
from herepy import HEREError
|
||||
from herepy.routing_api import InvalidCredentialsError
|
||||
from here_routing import HERERoutingError, HERERoutingUnauthorizedError
|
||||
import pytest
|
||||
|
||||
from homeassistant import config_entries, data_entry_flow
|
||||
|
@ -15,12 +14,10 @@ from homeassistant.components.here_travel_time.const import (
|
|||
CONF_ORIGIN_LATITUDE,
|
||||
CONF_ORIGIN_LONGITUDE,
|
||||
CONF_ROUTE_MODE,
|
||||
CONF_TRAFFIC_MODE,
|
||||
DOMAIN,
|
||||
ROUTE_MODE_FASTEST,
|
||||
TRAFFIC_MODE_ENABLED,
|
||||
TRAVEL_MODE_CAR,
|
||||
TRAVEL_MODE_PUBLIC_TIME_TABLE,
|
||||
TRAVEL_MODE_PUBLIC,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
CONF_API_KEY,
|
||||
|
@ -39,10 +36,11 @@ from homeassistant.util.unit_system import (
|
|||
|
||||
from .const import (
|
||||
API_KEY,
|
||||
CAR_DESTINATION_LATITUDE,
|
||||
CAR_DESTINATION_LONGITUDE,
|
||||
CAR_ORIGIN_LATITUDE,
|
||||
CAR_ORIGIN_LONGITUDE,
|
||||
DEFAULT_CONFIG,
|
||||
DESTINATION_LATITUDE,
|
||||
DESTINATION_LONGITUDE,
|
||||
ORIGIN_LATITUDE,
|
||||
ORIGIN_LONGITUDE,
|
||||
)
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
@ -83,12 +81,12 @@ async def option_init_result_fixture(hass: HomeAssistant) -> data_entry_flow.Flo
|
|||
domain=DOMAIN,
|
||||
unique_id="0123456789",
|
||||
data={
|
||||
CONF_ORIGIN_LATITUDE: float(CAR_ORIGIN_LATITUDE),
|
||||
CONF_ORIGIN_LONGITUDE: float(CAR_ORIGIN_LONGITUDE),
|
||||
CONF_DESTINATION_LATITUDE: float(CAR_DESTINATION_LATITUDE),
|
||||
CONF_DESTINATION_LONGITUDE: float(CAR_DESTINATION_LONGITUDE),
|
||||
CONF_ORIGIN_LATITUDE: float(ORIGIN_LATITUDE),
|
||||
CONF_ORIGIN_LONGITUDE: float(ORIGIN_LONGITUDE),
|
||||
CONF_DESTINATION_LATITUDE: float(DESTINATION_LATITUDE),
|
||||
CONF_DESTINATION_LONGITUDE: float(DESTINATION_LONGITUDE),
|
||||
CONF_API_KEY: API_KEY,
|
||||
CONF_MODE: TRAVEL_MODE_PUBLIC_TIME_TABLE,
|
||||
CONF_MODE: TRAVEL_MODE_PUBLIC,
|
||||
CONF_NAME: "test",
|
||||
},
|
||||
)
|
||||
|
@ -99,7 +97,6 @@ async def option_init_result_fixture(hass: HomeAssistant) -> data_entry_flow.Flo
|
|||
result = await hass.config_entries.options.async_configure(
|
||||
flow["flow_id"],
|
||||
user_input={
|
||||
CONF_TRAFFIC_MODE: TRAFFIC_MODE_ENABLED,
|
||||
CONF_ROUTE_MODE: ROUTE_MODE_FASTEST,
|
||||
CONF_UNIT_SYSTEM: CONF_UNIT_SYSTEM_METRIC,
|
||||
},
|
||||
|
@ -120,8 +117,8 @@ async def origin_step_result_fixture(
|
|||
origin_menu_result["flow_id"],
|
||||
{
|
||||
"origin": {
|
||||
"latitude": float(CAR_ORIGIN_LATITUDE),
|
||||
"longitude": float(CAR_ORIGIN_LONGITUDE),
|
||||
"latitude": float(ORIGIN_LATITUDE),
|
||||
"longitude": float(ORIGIN_LONGITUDE),
|
||||
"radius": 3.0,
|
||||
}
|
||||
},
|
||||
|
@ -170,8 +167,8 @@ async def test_step_origin_coordinates(
|
|||
menu_result["flow_id"],
|
||||
{
|
||||
"origin": {
|
||||
"latitude": float(CAR_ORIGIN_LATITUDE),
|
||||
"longitude": float(CAR_ORIGIN_LONGITUDE),
|
||||
"latitude": float(ORIGIN_LATITUDE),
|
||||
"longitude": float(ORIGIN_LONGITUDE),
|
||||
"radius": 3.0,
|
||||
}
|
||||
},
|
||||
|
@ -210,8 +207,8 @@ async def test_step_destination_coordinates(
|
|||
menu_result["flow_id"],
|
||||
{
|
||||
"destination": {
|
||||
"latitude": float(CAR_DESTINATION_LATITUDE),
|
||||
"longitude": float(CAR_DESTINATION_LONGITUDE),
|
||||
"latitude": float(DESTINATION_LATITUDE),
|
||||
"longitude": float(DESTINATION_LONGITUDE),
|
||||
"radius": 3.0,
|
||||
}
|
||||
},
|
||||
|
@ -223,10 +220,10 @@ async def test_step_destination_coordinates(
|
|||
assert entry.data == {
|
||||
CONF_NAME: "test",
|
||||
CONF_API_KEY: API_KEY,
|
||||
CONF_ORIGIN_LATITUDE: float(CAR_ORIGIN_LATITUDE),
|
||||
CONF_ORIGIN_LONGITUDE: float(CAR_ORIGIN_LONGITUDE),
|
||||
CONF_DESTINATION_LATITUDE: float(CAR_DESTINATION_LATITUDE),
|
||||
CONF_DESTINATION_LONGITUDE: float(CAR_DESTINATION_LONGITUDE),
|
||||
CONF_ORIGIN_LATITUDE: float(ORIGIN_LATITUDE),
|
||||
CONF_ORIGIN_LONGITUDE: float(ORIGIN_LONGITUDE),
|
||||
CONF_DESTINATION_LATITUDE: float(DESTINATION_LATITUDE),
|
||||
CONF_DESTINATION_LONGITUDE: float(DESTINATION_LONGITUDE),
|
||||
CONF_MODE: TRAVEL_MODE_CAR,
|
||||
}
|
||||
|
||||
|
@ -261,15 +258,14 @@ async def test_step_destination_entity(
|
|||
assert entry.data == {
|
||||
CONF_NAME: "test",
|
||||
CONF_API_KEY: API_KEY,
|
||||
CONF_ORIGIN_LATITUDE: float(CAR_ORIGIN_LATITUDE),
|
||||
CONF_ORIGIN_LONGITUDE: float(CAR_ORIGIN_LONGITUDE),
|
||||
CONF_ORIGIN_LATITUDE: float(ORIGIN_LATITUDE),
|
||||
CONF_ORIGIN_LONGITUDE: float(ORIGIN_LONGITUDE),
|
||||
CONF_DESTINATION_ENTITY_ID: "zone.home",
|
||||
CONF_MODE: TRAVEL_MODE_CAR,
|
||||
}
|
||||
assert entry.options == {
|
||||
CONF_UNIT_SYSTEM: expected_unit_option,
|
||||
CONF_ROUTE_MODE: ROUTE_MODE_FASTEST,
|
||||
CONF_TRAFFIC_MODE: TRAFFIC_MODE_ENABLED,
|
||||
CONF_ARRIVAL_TIME: None,
|
||||
CONF_DEPARTURE_TIME: None,
|
||||
}
|
||||
|
@ -282,8 +278,8 @@ async def test_form_invalid_auth(hass: HomeAssistant) -> None:
|
|||
)
|
||||
|
||||
with patch(
|
||||
"herepy.RoutingApi.public_transport_timetable",
|
||||
side_effect=InvalidCredentialsError,
|
||||
"here_routing.HERERoutingApi.route",
|
||||
side_effect=HERERoutingUnauthorizedError,
|
||||
):
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
|
@ -305,8 +301,8 @@ async def test_form_unknown_error(hass: HomeAssistant) -> None:
|
|||
)
|
||||
|
||||
with patch(
|
||||
"herepy.RoutingApi.public_transport_timetable",
|
||||
side_effect=HEREError,
|
||||
"here_routing.HERERoutingApi.route",
|
||||
side_effect=HERERoutingError,
|
||||
):
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
|
@ -327,15 +323,7 @@ async def test_options_flow(hass: HomeAssistant) -> None:
|
|||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
unique_id="0123456789",
|
||||
data={
|
||||
CONF_ORIGIN_LATITUDE: float(CAR_ORIGIN_LATITUDE),
|
||||
CONF_ORIGIN_LONGITUDE: float(CAR_ORIGIN_LONGITUDE),
|
||||
CONF_DESTINATION_LATITUDE: float(CAR_DESTINATION_LATITUDE),
|
||||
CONF_DESTINATION_LONGITUDE: float(CAR_DESTINATION_LONGITUDE),
|
||||
CONF_API_KEY: API_KEY,
|
||||
CONF_MODE: TRAVEL_MODE_CAR,
|
||||
CONF_NAME: "test",
|
||||
},
|
||||
data=DEFAULT_CONFIG,
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
|
@ -351,7 +339,6 @@ async def test_options_flow(hass: HomeAssistant) -> None:
|
|||
result = await hass.config_entries.options.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={
|
||||
CONF_TRAFFIC_MODE: TRAFFIC_MODE_ENABLED,
|
||||
CONF_ROUTE_MODE: ROUTE_MODE_FASTEST,
|
||||
CONF_UNIT_SYSTEM: CONF_UNIT_SYSTEM_IMPERIAL,
|
||||
},
|
||||
|
@ -381,7 +368,6 @@ async def test_options_flow_arrival_time_step(
|
|||
assert entry.options == {
|
||||
CONF_UNIT_SYSTEM: CONF_UNIT_SYSTEM_METRIC,
|
||||
CONF_ROUTE_MODE: ROUTE_MODE_FASTEST,
|
||||
CONF_TRAFFIC_MODE: TRAFFIC_MODE_ENABLED,
|
||||
CONF_ARRIVAL_TIME: "08:00:00",
|
||||
}
|
||||
|
||||
|
@ -407,7 +393,6 @@ async def test_options_flow_departure_time_step(
|
|||
assert entry.options == {
|
||||
CONF_UNIT_SYSTEM: CONF_UNIT_SYSTEM_METRIC,
|
||||
CONF_ROUTE_MODE: ROUTE_MODE_FASTEST,
|
||||
CONF_TRAFFIC_MODE: TRAFFIC_MODE_ENABLED,
|
||||
CONF_DEPARTURE_TIME: "08:00:00",
|
||||
}
|
||||
|
||||
|
@ -426,5 +411,4 @@ async def test_options_flow_no_time_step(
|
|||
assert entry.options == {
|
||||
CONF_UNIT_SYSTEM: CONF_UNIT_SYSTEM_METRIC,
|
||||
CONF_ROUTE_MODE: ROUTE_MODE_FASTEST,
|
||||
CONF_TRAFFIC_MODE: TRAFFIC_MODE_ENABLED,
|
||||
}
|
||||
|
|
|
@ -3,24 +3,10 @@
|
|||
import pytest
|
||||
|
||||
from homeassistant.components.here_travel_time.config_flow import default_options
|
||||
from homeassistant.components.here_travel_time.const import (
|
||||
CONF_DESTINATION_LATITUDE,
|
||||
CONF_DESTINATION_LONGITUDE,
|
||||
CONF_ORIGIN_LATITUDE,
|
||||
CONF_ORIGIN_LONGITUDE,
|
||||
DOMAIN,
|
||||
TRAVEL_MODE_CAR,
|
||||
)
|
||||
from homeassistant.const import CONF_API_KEY, CONF_MODE, CONF_NAME
|
||||
from homeassistant.components.here_travel_time.const import DOMAIN
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .const import (
|
||||
API_KEY,
|
||||
CAR_DESTINATION_LATITUDE,
|
||||
CAR_DESTINATION_LONGITUDE,
|
||||
CAR_ORIGIN_LATITUDE,
|
||||
CAR_ORIGIN_LONGITUDE,
|
||||
)
|
||||
from .const import DEFAULT_CONFIG
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
@ -31,15 +17,7 @@ async def test_unload_entry(hass: HomeAssistant) -> None:
|
|||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
unique_id="0123456789",
|
||||
data={
|
||||
CONF_ORIGIN_LATITUDE: float(CAR_ORIGIN_LATITUDE),
|
||||
CONF_ORIGIN_LONGITUDE: float(CAR_ORIGIN_LONGITUDE),
|
||||
CONF_DESTINATION_LATITUDE: float(CAR_DESTINATION_LATITUDE),
|
||||
CONF_DESTINATION_LONGITUDE: float(CAR_DESTINATION_LONGITUDE),
|
||||
CONF_API_KEY: API_KEY,
|
||||
CONF_MODE: TRAVEL_MODE_CAR,
|
||||
CONF_NAME: "test",
|
||||
},
|
||||
data=DEFAULT_CONFIG,
|
||||
options=default_options(hass),
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
|
|
|
@ -1,8 +1,14 @@
|
|||
"""The test for the HERE Travel Time sensor platform."""
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from herepy.here_enum import RouteMode
|
||||
from herepy.routing_api import NoRouteFoundError
|
||||
from here_routing import (
|
||||
HERERoutingError,
|
||||
Place,
|
||||
Return,
|
||||
RoutingMode,
|
||||
Spans,
|
||||
TransportMode,
|
||||
)
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.here_travel_time.config_flow import default_options
|
||||
|
@ -20,15 +26,14 @@ from homeassistant.components.here_travel_time.const import (
|
|||
ICON_BICYCLE,
|
||||
ICON_CAR,
|
||||
ICON_PEDESTRIAN,
|
||||
ICON_PUBLIC,
|
||||
ICON_TRUCK,
|
||||
NO_ROUTE_ERROR_MESSAGE,
|
||||
IMPERIAL_UNITS,
|
||||
METRIC_UNITS,
|
||||
ROUTE_MODE_FASTEST,
|
||||
TRAFFIC_MODE_ENABLED,
|
||||
TRAVEL_MODE_BICYCLE,
|
||||
TRAVEL_MODE_CAR,
|
||||
TRAVEL_MODE_PEDESTRIAN,
|
||||
TRAVEL_MODE_PUBLIC_TIME_TABLE,
|
||||
TRAVEL_MODE_PUBLIC,
|
||||
TRAVEL_MODE_TRUCK,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
|
@ -51,10 +56,10 @@ from homeassistant.setup import async_setup_component
|
|||
|
||||
from .const import (
|
||||
API_KEY,
|
||||
CAR_DESTINATION_LATITUDE,
|
||||
CAR_DESTINATION_LONGITUDE,
|
||||
CAR_ORIGIN_LATITUDE,
|
||||
CAR_ORIGIN_LONGITUDE,
|
||||
DESTINATION_LATITUDE,
|
||||
DESTINATION_LONGITUDE,
|
||||
ORIGIN_LATITUDE,
|
||||
ORIGIN_LONGITUDE,
|
||||
)
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
@ -69,9 +74,9 @@ from tests.common import MockConfigEntry
|
|||
"metric",
|
||||
None,
|
||||
None,
|
||||
"26",
|
||||
13.682,
|
||||
"30",
|
||||
23.903,
|
||||
"31",
|
||||
LENGTH_KILOMETERS,
|
||||
),
|
||||
(
|
||||
|
@ -80,8 +85,8 @@ from tests.common import MockConfigEntry
|
|||
"metric",
|
||||
None,
|
||||
None,
|
||||
"30",
|
||||
23.903,
|
||||
"26",
|
||||
13.682,
|
||||
"30",
|
||||
LENGTH_KILOMETERS,
|
||||
),
|
||||
|
@ -91,19 +96,8 @@ from tests.common import MockConfigEntry
|
|||
"imperial",
|
||||
None,
|
||||
None,
|
||||
"30",
|
||||
14.85263,
|
||||
"30",
|
||||
LENGTH_MILES,
|
||||
),
|
||||
(
|
||||
TRAVEL_MODE_PUBLIC_TIME_TABLE,
|
||||
ICON_PUBLIC,
|
||||
"imperial",
|
||||
"08:00:00",
|
||||
None,
|
||||
"30",
|
||||
14.85263,
|
||||
"26",
|
||||
8.5016,
|
||||
"30",
|
||||
LENGTH_MILES,
|
||||
),
|
||||
|
@ -113,9 +107,9 @@ from tests.common import MockConfigEntry
|
|||
"metric",
|
||||
None,
|
||||
"08:00:00",
|
||||
"26",
|
||||
13.682,
|
||||
"30",
|
||||
23.903,
|
||||
"31",
|
||||
LENGTH_KILOMETERS,
|
||||
),
|
||||
],
|
||||
|
@ -138,10 +132,10 @@ async def test_sensor(
|
|||
domain=DOMAIN,
|
||||
unique_id="0123456789",
|
||||
data={
|
||||
CONF_ORIGIN_LATITUDE: float(CAR_ORIGIN_LATITUDE),
|
||||
CONF_ORIGIN_LONGITUDE: float(CAR_ORIGIN_LONGITUDE),
|
||||
CONF_DESTINATION_LATITUDE: float(CAR_DESTINATION_LATITUDE),
|
||||
CONF_DESTINATION_LONGITUDE: float(CAR_DESTINATION_LONGITUDE),
|
||||
CONF_ORIGIN_LATITUDE: float(ORIGIN_LATITUDE),
|
||||
CONF_ORIGIN_LONGITUDE: float(ORIGIN_LONGITUDE),
|
||||
CONF_DESTINATION_LATITUDE: float(DESTINATION_LATITUDE),
|
||||
CONF_DESTINATION_LONGITUDE: float(DESTINATION_LONGITUDE),
|
||||
CONF_API_KEY: API_KEY,
|
||||
CONF_MODE: mode,
|
||||
CONF_NAME: "test",
|
||||
|
@ -161,10 +155,6 @@ async def test_sensor(
|
|||
|
||||
duration = hass.states.get("sensor.test_duration")
|
||||
assert duration.attributes.get("unit_of_measurement") == TIME_MINUTES
|
||||
assert (
|
||||
duration.attributes.get(ATTR_ATTRIBUTION)
|
||||
== "With the support of HERE Technologies. All information is provided without warranty of any kind."
|
||||
)
|
||||
assert duration.attributes.get(ATTR_ICON) == icon
|
||||
assert duration.state == expected_duration
|
||||
|
||||
|
@ -186,31 +176,21 @@ async def test_sensor(
|
|||
assert hass.states.get("sensor.test_origin").state == "22nd St NW"
|
||||
assert (
|
||||
hass.states.get("sensor.test_origin").attributes.get(ATTR_LATITUDE)
|
||||
== CAR_ORIGIN_LATITUDE
|
||||
== "38.8999937"
|
||||
)
|
||||
assert (
|
||||
hass.states.get("sensor.test_origin").attributes.get(ATTR_LONGITUDE)
|
||||
== CAR_ORIGIN_LONGITUDE
|
||||
)
|
||||
|
||||
assert hass.states.get("sensor.test_origin").state == "22nd St NW"
|
||||
assert (
|
||||
hass.states.get("sensor.test_origin").attributes.get(ATTR_LATITUDE)
|
||||
== CAR_ORIGIN_LATITUDE
|
||||
)
|
||||
assert (
|
||||
hass.states.get("sensor.test_origin").attributes.get(ATTR_LONGITUDE)
|
||||
== CAR_ORIGIN_LONGITUDE
|
||||
== "-77.0479682"
|
||||
)
|
||||
|
||||
assert hass.states.get("sensor.test_destination").state == "Service Rd S"
|
||||
assert (
|
||||
hass.states.get("sensor.test_destination").attributes.get(ATTR_LATITUDE)
|
||||
== CAR_DESTINATION_LATITUDE
|
||||
== "38.99997"
|
||||
)
|
||||
assert (
|
||||
hass.states.get("sensor.test_destination").attributes.get(ATTR_LONGITUDE)
|
||||
== CAR_DESTINATION_LONGITUDE
|
||||
== "-77.10014"
|
||||
)
|
||||
|
||||
|
||||
|
@ -227,8 +207,8 @@ async def test_circular_ref(hass: HomeAssistant, caplog):
|
|||
unique_id="0123456789",
|
||||
data={
|
||||
CONF_ORIGIN_ENTITY_ID: "test.first",
|
||||
CONF_DESTINATION_LATITUDE: float(CAR_DESTINATION_LATITUDE),
|
||||
CONF_DESTINATION_LONGITUDE: float(CAR_DESTINATION_LONGITUDE),
|
||||
CONF_DESTINATION_LATITUDE: float(DESTINATION_LATITUDE),
|
||||
CONF_DESTINATION_LONGITUDE: float(DESTINATION_LONGITUDE),
|
||||
CONF_API_KEY: API_KEY,
|
||||
CONF_MODE: TRAVEL_MODE_TRUCK,
|
||||
CONF_NAME: "test",
|
||||
|
@ -242,22 +222,69 @@ async def test_circular_ref(hass: HomeAssistant, caplog):
|
|||
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert "No coordinatnes found for test.first" in caplog.text
|
||||
assert "No coordinates found for test.first" in caplog.text
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("empty_attribution_response")
|
||||
async def test_no_attribution(hass: HomeAssistant):
|
||||
"""Test that an empty attribution is handled."""
|
||||
@pytest.mark.usefixtures("valid_response")
|
||||
@pytest.mark.parametrize(
|
||||
"unit_system,expected_distance",
|
||||
[
|
||||
(METRIC_UNITS, "1.883"),
|
||||
(IMPERIAL_UNITS, "1.1700419549829"),
|
||||
],
|
||||
)
|
||||
async def test_public_transport(
|
||||
hass: HomeAssistant, unit_system: str, expected_distance: str
|
||||
):
|
||||
"""Test that public transport mode is handled."""
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
unique_id="0123456789",
|
||||
data={
|
||||
CONF_ORIGIN_LATITUDE: float(CAR_ORIGIN_LATITUDE),
|
||||
CONF_ORIGIN_LONGITUDE: float(CAR_ORIGIN_LONGITUDE),
|
||||
CONF_DESTINATION_LATITUDE: float(CAR_DESTINATION_LATITUDE),
|
||||
CONF_DESTINATION_LONGITUDE: float(CAR_DESTINATION_LONGITUDE),
|
||||
CONF_ORIGIN_LATITUDE: float(ORIGIN_LATITUDE),
|
||||
CONF_ORIGIN_LONGITUDE: float(ORIGIN_LONGITUDE),
|
||||
CONF_DESTINATION_LATITUDE: float(DESTINATION_LATITUDE),
|
||||
CONF_DESTINATION_LONGITUDE: float(DESTINATION_LONGITUDE),
|
||||
CONF_API_KEY: API_KEY,
|
||||
CONF_MODE: TRAVEL_MODE_TRUCK,
|
||||
CONF_MODE: TRAVEL_MODE_PUBLIC,
|
||||
CONF_NAME: "test",
|
||||
},
|
||||
options={
|
||||
CONF_ROUTE_MODE: ROUTE_MODE_FASTEST,
|
||||
CONF_ARRIVAL_TIME: "08:00:00",
|
||||
CONF_DEPARTURE_TIME: None,
|
||||
CONF_UNIT_SYSTEM: unit_system,
|
||||
},
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert (
|
||||
hass.states.get("sensor.test_duration").attributes.get(ATTR_ATTRIBUTION)
|
||||
== "http://creativecommons.org/licenses/by/3.0/it/,Some line names used in this product or service were edited to align with official transportation maps."
|
||||
)
|
||||
assert hass.states.get("sensor.test_distance").state == pytest.approx(
|
||||
expected_distance
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("no_attribution_response")
|
||||
async def test_no_attribution_response(hass: HomeAssistant):
|
||||
"""Test that no_attribution is handled."""
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
unique_id="0123456789",
|
||||
data={
|
||||
CONF_ORIGIN_LATITUDE: float(ORIGIN_LATITUDE),
|
||||
CONF_ORIGIN_LONGITUDE: float(ORIGIN_LONGITUDE),
|
||||
CONF_DESTINATION_LATITUDE: float(DESTINATION_LATITUDE),
|
||||
CONF_DESTINATION_LONGITUDE: float(DESTINATION_LONGITUDE),
|
||||
CONF_API_KEY: API_KEY,
|
||||
CONF_MODE: TRAVEL_MODE_PUBLIC,
|
||||
CONF_NAME: "test",
|
||||
},
|
||||
options=default_options(hass),
|
||||
|
@ -280,8 +307,8 @@ async def test_entity_ids(hass: HomeAssistant, valid_response: MagicMock):
|
|||
"zone": [
|
||||
{
|
||||
"name": "Origin",
|
||||
"latitude": CAR_ORIGIN_LATITUDE,
|
||||
"longitude": CAR_ORIGIN_LONGITUDE,
|
||||
"latitude": ORIGIN_LATITUDE,
|
||||
"longitude": ORIGIN_LONGITUDE,
|
||||
"radius": 250,
|
||||
"passive": False,
|
||||
},
|
||||
|
@ -292,8 +319,8 @@ async def test_entity_ids(hass: HomeAssistant, valid_response: MagicMock):
|
|||
"device_tracker.test",
|
||||
"not_home",
|
||||
{
|
||||
"latitude": float(CAR_DESTINATION_LATITUDE),
|
||||
"longitude": float(CAR_DESTINATION_LONGITUDE),
|
||||
"latitude": float(DESTINATION_LATITUDE),
|
||||
"longitude": float(DESTINATION_LONGITUDE),
|
||||
},
|
||||
)
|
||||
entry = MockConfigEntry(
|
||||
|
@ -315,19 +342,17 @@ async def test_entity_ids(hass: HomeAssistant, valid_response: MagicMock):
|
|||
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert hass.states.get("sensor.test_distance").state == "23.903"
|
||||
assert hass.states.get("sensor.test_distance").state == "13.682"
|
||||
|
||||
valid_response.assert_called_with(
|
||||
[CAR_ORIGIN_LATITUDE, CAR_ORIGIN_LONGITUDE],
|
||||
[CAR_DESTINATION_LATITUDE, CAR_DESTINATION_LONGITUDE],
|
||||
True,
|
||||
[
|
||||
RouteMode[ROUTE_MODE_FASTEST],
|
||||
RouteMode[TRAVEL_MODE_TRUCK],
|
||||
RouteMode[TRAFFIC_MODE_ENABLED],
|
||||
],
|
||||
arrival=None,
|
||||
departure="now",
|
||||
transport_mode=TransportMode.TRUCK,
|
||||
origin=Place(ORIGIN_LATITUDE, ORIGIN_LONGITUDE),
|
||||
destination=Place(DESTINATION_LATITUDE, DESTINATION_LONGITUDE),
|
||||
routing_mode=RoutingMode.FAST,
|
||||
arrival_time=None,
|
||||
departure_time=None,
|
||||
return_values=[Return.POLYINE, Return.SUMMARY],
|
||||
spans=[Spans.NAMES],
|
||||
)
|
||||
|
||||
|
||||
|
@ -338,8 +363,8 @@ async def test_destination_entity_not_found(hass: HomeAssistant, caplog):
|
|||
domain=DOMAIN,
|
||||
unique_id="0123456789",
|
||||
data={
|
||||
CONF_ORIGIN_LATITUDE: float(CAR_ORIGIN_LATITUDE),
|
||||
CONF_ORIGIN_LONGITUDE: float(CAR_ORIGIN_LONGITUDE),
|
||||
CONF_ORIGIN_LATITUDE: float(ORIGIN_LATITUDE),
|
||||
CONF_ORIGIN_LONGITUDE: float(ORIGIN_LONGITUDE),
|
||||
CONF_DESTINATION_ENTITY_ID: "device_tracker.test",
|
||||
CONF_API_KEY: API_KEY,
|
||||
CONF_MODE: TRAVEL_MODE_TRUCK,
|
||||
|
@ -365,8 +390,8 @@ async def test_origin_entity_not_found(hass: HomeAssistant, caplog):
|
|||
unique_id="0123456789",
|
||||
data={
|
||||
CONF_ORIGIN_ENTITY_ID: "device_tracker.test",
|
||||
CONF_DESTINATION_LATITUDE: float(CAR_DESTINATION_LATITUDE),
|
||||
CONF_DESTINATION_LONGITUDE: float(CAR_DESTINATION_LONGITUDE),
|
||||
CONF_DESTINATION_LATITUDE: float(DESTINATION_LATITUDE),
|
||||
CONF_DESTINATION_LONGITUDE: float(DESTINATION_LONGITUDE),
|
||||
CONF_API_KEY: API_KEY,
|
||||
CONF_MODE: TRAVEL_MODE_TRUCK,
|
||||
CONF_NAME: "test",
|
||||
|
@ -394,8 +419,8 @@ async def test_invalid_destination_entity_state(hass: HomeAssistant, caplog):
|
|||
domain=DOMAIN,
|
||||
unique_id="0123456789",
|
||||
data={
|
||||
CONF_ORIGIN_LATITUDE: float(CAR_ORIGIN_LATITUDE),
|
||||
CONF_ORIGIN_LONGITUDE: float(CAR_ORIGIN_LONGITUDE),
|
||||
CONF_ORIGIN_LATITUDE: float(ORIGIN_LATITUDE),
|
||||
CONF_ORIGIN_LONGITUDE: float(ORIGIN_LONGITUDE),
|
||||
CONF_DESTINATION_ENTITY_ID: "device_tracker.test",
|
||||
CONF_API_KEY: API_KEY,
|
||||
CONF_MODE: TRAVEL_MODE_TRUCK,
|
||||
|
@ -425,8 +450,8 @@ async def test_invalid_origin_entity_state(hass: HomeAssistant, caplog):
|
|||
unique_id="0123456789",
|
||||
data={
|
||||
CONF_ORIGIN_ENTITY_ID: "device_tracker.test",
|
||||
CONF_DESTINATION_LATITUDE: float(CAR_DESTINATION_LATITUDE),
|
||||
CONF_DESTINATION_LONGITUDE: float(CAR_DESTINATION_LONGITUDE),
|
||||
CONF_DESTINATION_LATITUDE: float(DESTINATION_LATITUDE),
|
||||
CONF_DESTINATION_LONGITUDE: float(DESTINATION_LONGITUDE),
|
||||
CONF_API_KEY: API_KEY,
|
||||
CONF_MODE: TRAVEL_MODE_TRUCK,
|
||||
CONF_NAME: "test",
|
||||
|
@ -446,17 +471,19 @@ async def test_invalid_origin_entity_state(hass: HomeAssistant, caplog):
|
|||
async def test_route_not_found(hass: HomeAssistant, caplog):
|
||||
"""Test that route not found error is correctly handled."""
|
||||
with patch(
|
||||
"herepy.RoutingApi.public_transport_timetable",
|
||||
side_effect=NoRouteFoundError,
|
||||
"here_routing.HERERoutingApi.route",
|
||||
side_effect=HERERoutingError(
|
||||
"Route calculation failed: Couldn't find a route."
|
||||
),
|
||||
):
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
unique_id="0123456789",
|
||||
data={
|
||||
CONF_ORIGIN_LATITUDE: float(CAR_ORIGIN_LATITUDE),
|
||||
CONF_ORIGIN_LONGITUDE: float(CAR_ORIGIN_LONGITUDE),
|
||||
CONF_DESTINATION_LATITUDE: float(CAR_DESTINATION_LATITUDE),
|
||||
CONF_DESTINATION_LONGITUDE: float(CAR_DESTINATION_LONGITUDE),
|
||||
CONF_ORIGIN_LATITUDE: float(ORIGIN_LATITUDE),
|
||||
CONF_ORIGIN_LONGITUDE: float(ORIGIN_LONGITUDE),
|
||||
CONF_DESTINATION_LATITUDE: float(DESTINATION_LATITUDE),
|
||||
CONF_DESTINATION_LONGITUDE: float(DESTINATION_LONGITUDE),
|
||||
CONF_API_KEY: API_KEY,
|
||||
CONF_MODE: TRAVEL_MODE_TRUCK,
|
||||
CONF_NAME: "test",
|
||||
|
@ -469,4 +496,4 @@ async def test_route_not_found(hass: HomeAssistant, caplog):
|
|||
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert NO_ROUTE_ERROR_MESSAGE in caplog.text
|
||||
assert "Route calculation failed: Couldn't find a route." in caplog.text
|
||||
|
|
Loading…
Add table
Reference in a new issue