diff --git a/homeassistant/components/here_travel_time/__init__.py b/homeassistant/components/here_travel_time/__init__.py
index b9ffa3e4baa..28ecf3f3d8d 100644
--- a/homeassistant/components/here_travel_time/__init__.py
+++ b/homeassistant/components/here_travel_time/__init__.py
@@ -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."""
diff --git a/homeassistant/components/here_travel_time/config_flow.py b/homeassistant/components/here_travel_time/config_flow.py
index 38bd1742c91..c0b00ffc876 100644
--- a/homeassistant/components/here_travel_time/config_flow.py
+++ b/homeassistant/components/here_travel_time/config_flow.py
@@ -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(
diff --git a/homeassistant/components/here_travel_time/const.py b/homeassistant/components/here_travel_time/const.py
index ea0dc5c136e..4fce053f768 100644
--- a/homeassistant/components/here_travel_time/const.py
+++ b/homeassistant/components/here_travel_time/const.py
@@ -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"
diff --git a/homeassistant/components/here_travel_time/coordinator.py b/homeassistant/components/here_travel_time/coordinator.py
new file mode 100644
index 00000000000..de9bb9f1c60
--- /dev/null
+++ b/homeassistant/components/here_travel_time/coordinator.py
@@ -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."""
diff --git a/homeassistant/components/here_travel_time/manifest.json b/homeassistant/components/here_travel_time/manifest.json
index 68370311254..8efcf29b6b0 100644
--- a/homeassistant/components/here_travel_time/manifest.json
+++ b/homeassistant/components/here_travel_time/manifest.json
@@ -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"]
}
diff --git a/homeassistant/components/here_travel_time/model.py b/homeassistant/components/here_travel_time/model.py
index 7310ac24e77..737aae78a03 100644
--- a/homeassistant/components/here_travel_time/model.py
+++ b/homeassistant/components/here_travel_time/model.py
@@ -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
diff --git a/homeassistant/components/here_travel_time/sensor.py b/homeassistant/components/here_travel_time/sensor.py
index 1ee0087eab7..1fec002377e 100644
--- a/homeassistant/components/here_travel_time/sensor.py
+++ b/homeassistant/components/here_travel_time/sensor.py
@@ -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(
diff --git a/requirements_all.txt b/requirements_all.txt
index 03778c2609e..9252d244a53 100644
--- a/requirements_all.txt
+++ b/requirements_all.txt
@@ -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
diff --git a/requirements_test_all.txt b/requirements_test_all.txt
index 983c60b3f74..3a8681f3d9a 100644
--- a/requirements_test_all.txt
+++ b/requirements_test_all.txt
@@ -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
diff --git a/tests/components/here_travel_time/conftest.py b/tests/components/here_travel_time/conftest.py
index 368b070428e..8069583df76 100644
--- a/tests/components/here_travel_time/conftest.py
+++ b/tests/components/here_travel_time/conftest.py
@@ -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
diff --git a/tests/components/here_travel_time/const.py b/tests/components/here_travel_time/const.py
index 0cc3143bc0b..167fd51dc5b 100644
--- a/tests/components/here_travel_time/const.py
+++ b/tests/components/here_travel_time/const.py
@@ -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",
+}
diff --git a/tests/components/here_travel_time/fixtures/car_response.json b/tests/components/here_travel_time/fixtures/car_response.json
index cd479b2c947..99a2d9ef051 100644
--- a/tests/components/here_travel_time/fixtures/car_response.json
+++ b/tests/components/here_travel_time/fixtures/car_response.json
@@ -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 I St NW on 22nd St NW. Go for 279 m.",
- "travelTime": 95,
- "length": 279,
- "id": "M1",
- "_type": "PrivateTransportManeuverType"
- },
- {
- "position": {
- "latitude": 38.9021051,
- "longitude": -77.048825
- },
- "instruction": "Turn left toward Pennsylvania Ave NW. Go for 71 m.",
- "travelTime": 21,
- "length": 71,
- "id": "M2",
- "_type": "PrivateTransportManeuverType"
- },
- {
- "position": {
- "latitude": 38.902545,
- "longitude": -77.0494151
- },
- "instruction": "Take the 3rd exit from Washington Cir NW roundabout onto K St NW. Go for 352 m.",
- "travelTime": 90,
- "length": 352,
- "id": "M3",
- "_type": "PrivateTransportManeuverType"
- },
- {
- "position": {
- "latitude": 38.9026523,
- "longitude": -77.0529449
- },
- "instruction": "Keep left onto K St NW (US-29). Go for 201 m.",
- "travelTime": 30,
- "length": 201,
- "id": "M4",
- "_type": "PrivateTransportManeuverType"
- },
- {
- "position": {
- "latitude": 38.9025235,
- "longitude": -77.0552516
- },
- "instruction": "Keep right onto Whitehurst Fwy (US-29). Go for 1.4 km.",
- "travelTime": 131,
- "length": 1381,
- "id": "M5",
- "_type": "PrivateTransportManeuverType"
- },
- {
- "position": {
- "latitude": 38.9050448,
- "longitude": -77.0701969
- },
- "instruction": "Turn left onto M St NW. Go for 784 m.",
- "travelTime": 78,
- "length": 784,
- "id": "M6",
- "_type": "PrivateTransportManeuverType"
- },
- {
- "position": {
- "latitude": 38.9060318,
- "longitude": -77.0790696
- },
- "instruction": "Turn slightly left onto Canal Rd NW. Go for 4.2 km.",
- "travelTime": 277,
- "length": 4230,
- "id": "M7",
- "_type": "PrivateTransportManeuverType"
- },
- {
- "position": {
- "latitude": 38.9303219,
- "longitude": -77.1117926
- },
- "instruction": "Continue on Clara Barton Pkwy. Go for 844 m.",
- "travelTime": 55,
- "length": 844,
- "id": "M8",
- "_type": "PrivateTransportManeuverType"
- },
- {
- "position": {
- "latitude": 38.9368558,
- "longitude": -77.1166742
- },
- "instruction": "Continue on Clara Barton Pkwy. Go for 4.7 km.",
- "travelTime": 298,
- "length": 4652,
- "id": "M9",
- "_type": "PrivateTransportManeuverType"
- },
- {
- "position": {
- "latitude": 38.9706838,
- "longitude": -77.1461463
- },
- "instruction": "Keep right onto Cabin John Pkwy N toward I-495 N. Go for 2.1 km.",
- "travelTime": 91,
- "length": 2069,
- "id": "M10",
- "_type": "PrivateTransportManeuverType"
- },
- {
- "position": {
- "latitude": 38.9858222,
- "longitude": -77.1571326
- },
- "instruction": "Take left ramp onto I-495 N (Capital Beltway). Go for 5.5 km.",
- "travelTime": 238,
- "length": 5538,
- "id": "M11",
- "_type": "PrivateTransportManeuverType"
- },
- {
- "position": {
- "latitude": 39.0153587,
- "longitude": -77.1221781
- },
- "instruction": "Take exit 36 toward Bethesda onto MD-187 S (Old Georgetown Rd). Go for 2.4 km.",
- "travelTime": 211,
- "length": 2365,
- "id": "M12",
- "_type": "PrivateTransportManeuverType"
- },
- {
- "position": {
- "latitude": 38.9981818,
- "longitude": -77.1093571
- },
- "instruction": "Turn left onto Lincoln Dr. Go for 506 m.",
- "travelTime": 127,
- "length": 506,
- "id": "M13",
- "_type": "PrivateTransportManeuverType"
- },
- {
- "position": {
- "latitude": 38.9987397,
- "longitude": -77.1037138
- },
- "instruction": "Turn right onto Service Rd W. Go for 121 m.",
- "travelTime": 36,
- "length": 121,
- "id": "M14",
- "_type": "PrivateTransportManeuverType"
- },
- {
- "position": {
- "latitude": 38.9976454,
- "longitude": -77.1036172
- },
- "instruction": "Turn left onto Service Rd S. Go for 510 m.",
- "travelTime": 106,
- "length": 510,
- "id": "M15",
- "_type": "PrivateTransportManeuverType"
- },
- {
- "position": {
- "latitude": 38.9999735,
- "longitude": -77.100141
- },
- "instruction": "Arrive at Service Rd S. 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 23.9 km and 31 mins.",
- "travelTime": 1861,
- "_type": "RouteSummaryType"
- }
- }
- ],
- "language": "en-us",
- "sourceAttribution": {
- "attribution": "With the support of HERE Technologies. 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"
+ }
}
]
}
- }
+ ]
}
diff --git a/tests/components/here_travel_time/fixtures/empty_attribution_response.json b/tests/components/here_travel_time/fixtures/empty_attribution_response.json
deleted file mode 100644
index cc1bb20a373..00000000000
--- a/tests/components/here_travel_time/fixtures/empty_attribution_response.json
+++ /dev/null
@@ -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 Service Rd S. 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 23.9 km and 31 mins.",
- "travelTime": 1861,
- "_type": "RouteSummaryType"
- }
- }
- ],
- "language": "en-us",
- "sourceAttribution": {}
- }
-}
diff --git a/tests/components/here_travel_time/fixtures/no_attribution_transit_route_response.json b/tests/components/here_travel_time/fixtures/no_attribution_transit_route_response.json
new file mode 100644
index 00000000000..1057a4a66b1
--- /dev/null
+++ b/tests/components/here_travel_time/fixtures/no_attribution_transit_route_response.json
@@ -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"
+ }
+ }
+ ]
+ }
+ ]
+}
diff --git a/tests/components/here_travel_time/fixtures/transit_route_response.json b/tests/components/here_travel_time/fixtures/transit_route_response.json
new file mode 100644
index 00000000000..72b04a2d10e
--- /dev/null
+++ b/tests/components/here_travel_time/fixtures/transit_route_response.json
@@ -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"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/tests/components/here_travel_time/test_config_flow.py b/tests/components/here_travel_time/test_config_flow.py
index 120ffd828bc..d22d36a4d66 100644
--- a/tests/components/here_travel_time/test_config_flow.py
+++ b/tests/components/here_travel_time/test_config_flow.py
@@ -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,
}
diff --git a/tests/components/here_travel_time/test_init.py b/tests/components/here_travel_time/test_init.py
index 05b7f6983db..b8bbdec241a 100644
--- a/tests/components/here_travel_time/test_init.py
+++ b/tests/components/here_travel_time/test_init.py
@@ -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)
diff --git a/tests/components/here_travel_time/test_sensor.py b/tests/components/here_travel_time/test_sensor.py
index 5cc4802d253..8a6f7f3f5d3 100644
--- a/tests/components/here_travel_time/test_sensor.py
+++ b/tests/components/here_travel_time/test_sensor.py
@@ -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