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:
Kevin Stillhammer 2022-11-16 04:04:41 +01:00 committed by GitHub
parent 495ca67e8b
commit aedbfdabee
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 1044 additions and 877 deletions

View file

@ -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."""

View file

@ -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(

View file

@ -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"

View 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."""

View file

@ -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"]
}

View file

@ -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

View file

@ -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(

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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",
}

View file

@ -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"
}
}
]
}
}
]
}

View file

@ -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": {}
}
}

View file

@ -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"
}
}
]
}
]
}

View file

@ -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"
}
]
}
]
}
]
}

View file

@ -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,
}

View file

@ -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)

View file

@ -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