From 95d4dc678cf79cf91cc7e38061532af7b3132e77 Mon Sep 17 00:00:00 2001 From: Kevin Stillhammer Date: Fri, 25 Jul 2025 12:14:36 +0200 Subject: [PATCH] Add option traffic_mode in here_travel_time (#146676) --- .../components/here_travel_time/__init__.py | 31 ++++++++++- .../here_travel_time/config_flow.py | 17 +++++- .../components/here_travel_time/const.py | 1 + .../here_travel_time/coordinator.py | 12 ++++- .../components/here_travel_time/model.py | 3 +- .../components/here_travel_time/strings.json | 5 +- .../here_travel_time/test_config_flow.py | 24 ++++++++- .../components/here_travel_time/test_init.py | 32 +++++++++++- .../here_travel_time/test_sensor.py | 52 +++++++++++++++++-- 9 files changed, 166 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/here_travel_time/__init__.py b/homeassistant/components/here_travel_time/__init__.py index 5393dfa5050..741a9a1058c 100644 --- a/homeassistant/components/here_travel_time/__init__.py +++ b/homeassistant/components/here_travel_time/__init__.py @@ -2,11 +2,13 @@ from __future__ import annotations +import logging + from homeassistant.const import CONF_API_KEY, CONF_MODE, Platform from homeassistant.core import HomeAssistant from homeassistant.helpers.start import async_at_started -from .const import TRAVEL_MODE_PUBLIC +from .const import CONF_TRAFFIC_MODE, TRAVEL_MODE_PUBLIC from .coordinator import ( HereConfigEntry, HERERoutingDataUpdateCoordinator, @@ -15,6 +17,8 @@ from .coordinator import ( PLATFORMS = [Platform.SENSOR] +_LOGGER = logging.getLogger(__name__) + async def async_setup_entry(hass: HomeAssistant, config_entry: HereConfigEntry) -> bool: """Set up HERE Travel Time from a config entry.""" @@ -43,3 +47,28 @@ async def async_unload_entry( ) -> bool: """Unload a config entry.""" return await hass.config_entries.async_unload_platforms(config_entry, PLATFORMS) + + +async def async_migrate_entry( + hass: HomeAssistant, config_entry: HereConfigEntry +) -> bool: + """Migrate an old config entry.""" + + if config_entry.version == 1 and config_entry.minor_version == 1: + _LOGGER.debug( + "Migrating from version %s.%s", + config_entry.version, + config_entry.minor_version, + ) + options = dict(config_entry.options) + options[CONF_TRAFFIC_MODE] = True + + hass.config_entries.async_update_entry( + config_entry, options=options, version=1, minor_version=2 + ) + _LOGGER.debug( + "Migration to version %s.%s successful", + config_entry.version, + config_entry.minor_version, + ) + return True diff --git a/homeassistant/components/here_travel_time/config_flow.py b/homeassistant/components/here_travel_time/config_flow.py index 6425b5ffbed..5ff0a68bc9a 100644 --- a/homeassistant/components/here_travel_time/config_flow.py +++ b/homeassistant/components/here_travel_time/config_flow.py @@ -33,6 +33,7 @@ from homeassistant.const import ( from homeassistant.core import callback from homeassistant.helpers import config_validation as cv from homeassistant.helpers.selector import ( + BooleanSelector, EntitySelector, LocationSelector, TimeSelector, @@ -50,6 +51,7 @@ from .const import ( CONF_ORIGIN_LATITUDE, CONF_ORIGIN_LONGITUDE, CONF_ROUTE_MODE, + CONF_TRAFFIC_MODE, DEFAULT_NAME, DOMAIN, ROUTE_MODE_FASTEST, @@ -65,6 +67,7 @@ DEFAULT_OPTIONS = { CONF_ROUTE_MODE: ROUTE_MODE_FASTEST, CONF_ARRIVAL_TIME: None, CONF_DEPARTURE_TIME: None, + CONF_TRAFFIC_MODE: True, } @@ -102,6 +105,7 @@ class HERETravelTimeConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for HERE Travel Time.""" VERSION = 1 + MINOR_VERSION = 2 def __init__(self) -> None: """Init Config Flow.""" @@ -307,7 +311,9 @@ class HERETravelTimeOptionsFlow(OptionsFlow): """Manage the HERE Travel Time options.""" if user_input is not None: self._config = user_input - return await self.async_step_time_menu() + if self._config[CONF_TRAFFIC_MODE]: + return await self.async_step_time_menu() + return self.async_create_entry(title="", data=self._config) schema = self.add_suggested_values_to_schema( vol.Schema( @@ -318,12 +324,21 @@ class HERETravelTimeOptionsFlow(OptionsFlow): CONF_ROUTE_MODE, DEFAULT_OPTIONS[CONF_ROUTE_MODE] ), ): vol.In(ROUTE_MODES), + vol.Optional( + CONF_TRAFFIC_MODE, + default=self.config_entry.options.get( + CONF_TRAFFIC_MODE, DEFAULT_OPTIONS[CONF_TRAFFIC_MODE] + ), + ): BooleanSelector(), } ), { CONF_ROUTE_MODE: self.config_entry.options.get( CONF_ROUTE_MODE, DEFAULT_OPTIONS[CONF_ROUTE_MODE] ), + CONF_TRAFFIC_MODE: self.config_entry.options.get( + CONF_TRAFFIC_MODE, DEFAULT_OPTIONS[CONF_TRAFFIC_MODE] + ), }, ) diff --git a/homeassistant/components/here_travel_time/const.py b/homeassistant/components/here_travel_time/const.py index 785070cd3b1..cc208d95abe 100644 --- a/homeassistant/components/here_travel_time/const.py +++ b/homeassistant/components/here_travel_time/const.py @@ -19,6 +19,7 @@ CONF_ARRIVAL = "arrival" CONF_DEPARTURE = "departure" CONF_ARRIVAL_TIME = "arrival_time" CONF_DEPARTURE_TIME = "departure_time" +CONF_TRAFFIC_MODE = "traffic_mode" DEFAULT_NAME = "HERE Travel Time" diff --git a/homeassistant/components/here_travel_time/coordinator.py b/homeassistant/components/here_travel_time/coordinator.py index d8c698554c9..0e447770ca9 100644 --- a/homeassistant/components/here_travel_time/coordinator.py +++ b/homeassistant/components/here_travel_time/coordinator.py @@ -13,6 +13,7 @@ from here_routing import ( Return, RoutingMode, Spans, + TrafficMode, TransportMode, ) import here_transit @@ -44,6 +45,7 @@ from .const import ( CONF_ORIGIN_LATITUDE, CONF_ORIGIN_LONGITUDE, CONF_ROUTE_MODE, + CONF_TRAFFIC_MODE, DEFAULT_SCAN_INTERVAL, DOMAIN, ROUTE_MODE_FASTEST, @@ -87,7 +89,7 @@ class HERERoutingDataUpdateCoordinator(DataUpdateCoordinator[HERETravelTimeData] _LOGGER.debug( ( "Requesting route for origin: %s, destination: %s, route_mode: %s," - " mode: %s, arrival: %s, departure: %s" + " mode: %s, arrival: %s, departure: %s, traffic_mode: %s" ), params.origin, params.destination, @@ -95,6 +97,7 @@ class HERERoutingDataUpdateCoordinator(DataUpdateCoordinator[HERETravelTimeData] TransportMode(params.travel_mode), params.arrival, params.departure, + params.traffic_mode, ) try: @@ -109,6 +112,7 @@ class HERERoutingDataUpdateCoordinator(DataUpdateCoordinator[HERETravelTimeData] routing_mode=params.route_mode, arrival_time=params.arrival, departure_time=params.departure, + traffic_mode=params.traffic_mode, return_values=[Return.POLYINE, Return.SUMMARY], spans=[Spans.NAMES], ) @@ -350,6 +354,11 @@ def prepare_parameters( if config_entry.options[CONF_ROUTE_MODE] == ROUTE_MODE_FASTEST else RoutingMode.SHORT ) + traffic_mode = ( + TrafficMode.DISABLED + if config_entry.options[CONF_TRAFFIC_MODE] is False + else TrafficMode.DEFAULT + ) return HERETravelTimeAPIParams( destination=destination, @@ -358,6 +367,7 @@ def prepare_parameters( route_mode=route_mode, arrival=arrival, departure=departure, + traffic_mode=traffic_mode, ) diff --git a/homeassistant/components/here_travel_time/model.py b/homeassistant/components/here_travel_time/model.py index a0534d2ff01..deb886f6805 100644 --- a/homeassistant/components/here_travel_time/model.py +++ b/homeassistant/components/here_travel_time/model.py @@ -6,7 +6,7 @@ from dataclasses import dataclass from datetime import datetime from typing import TypedDict -from here_routing import RoutingMode +from here_routing import RoutingMode, TrafficMode class HERETravelTimeData(TypedDict): @@ -32,3 +32,4 @@ class HERETravelTimeAPIParams: route_mode: RoutingMode arrival: datetime | None departure: datetime | None + traffic_mode: TrafficMode diff --git a/homeassistant/components/here_travel_time/strings.json b/homeassistant/components/here_travel_time/strings.json index 89350261299..639be3326f9 100644 --- a/homeassistant/components/here_travel_time/strings.json +++ b/homeassistant/components/here_travel_time/strings.json @@ -60,8 +60,11 @@ "step": { "init": { "data": { - "traffic_mode": "Traffic mode", + "traffic_mode": "Use traffic and time-aware routing", "route_mode": "Route mode" + }, + "data_description": { + "traffic_mode": "Needed for defining arrival/departure times" } }, "time_menu": { diff --git a/tests/components/here_travel_time/test_config_flow.py b/tests/components/here_travel_time/test_config_flow.py index ce210813fb2..82c75471896 100644 --- a/tests/components/here_travel_time/test_config_flow.py +++ b/tests/components/here_travel_time/test_config_flow.py @@ -6,7 +6,10 @@ from here_routing import HERERoutingError, HERERoutingUnauthorizedError import pytest from homeassistant import config_entries -from homeassistant.components.here_travel_time.config_flow import DEFAULT_OPTIONS +from homeassistant.components.here_travel_time.config_flow import ( + DEFAULT_OPTIONS, + HERETravelTimeConfigFlow, +) from homeassistant.components.here_travel_time.const import ( CONF_ARRIVAL_TIME, CONF_DEPARTURE_TIME, @@ -17,6 +20,7 @@ from homeassistant.components.here_travel_time.const import ( CONF_ORIGIN_LATITUDE, CONF_ORIGIN_LONGITUDE, CONF_ROUTE_MODE, + CONF_TRAFFIC_MODE, DOMAIN, ROUTE_MODE_FASTEST, TRAVEL_MODE_BICYCLE, @@ -86,6 +90,8 @@ async def option_init_result_fixture( CONF_MODE: TRAVEL_MODE_PUBLIC, CONF_NAME: "test", }, + version=HERETravelTimeConfigFlow.VERSION, + minor_version=HERETravelTimeConfigFlow.MINOR_VERSION, ) entry.add_to_hass(hass) await hass.config_entries.async_setup(entry.entry_id) @@ -249,6 +255,7 @@ async def test_step_destination_entity( CONF_ROUTE_MODE: ROUTE_MODE_FASTEST, CONF_ARRIVAL_TIME: None, CONF_DEPARTURE_TIME: None, + CONF_TRAFFIC_MODE: True, } @@ -317,6 +324,8 @@ async def do_common_reconfiguration_steps(hass: HomeAssistant) -> None: unique_id="0123456789", data=DEFAULT_CONFIG, options=DEFAULT_OPTIONS, + version=HERETravelTimeConfigFlow.VERSION, + minor_version=HERETravelTimeConfigFlow.MINOR_VERSION, ) entry.add_to_hass(hass) @@ -398,6 +407,8 @@ async def test_options_flow(hass: HomeAssistant) -> None: domain=DOMAIN, unique_id="0123456789", data=DEFAULT_CONFIG, + version=HERETravelTimeConfigFlow.VERSION, + minor_version=HERETravelTimeConfigFlow.MINOR_VERSION, ) entry.add_to_hass(hass) @@ -414,10 +425,16 @@ async def test_options_flow(hass: HomeAssistant) -> None: result["flow_id"], user_input={ CONF_ROUTE_MODE: ROUTE_MODE_FASTEST, + CONF_TRAFFIC_MODE: False, }, ) - assert result["type"] is FlowResultType.MENU + assert result["type"] is FlowResultType.CREATE_ENTRY + entry = hass.config_entries.async_entries(DOMAIN)[0] + assert entry.options == { + CONF_ROUTE_MODE: ROUTE_MODE_FASTEST, + CONF_TRAFFIC_MODE: False, + } @pytest.mark.usefixtures("valid_response") @@ -441,6 +458,7 @@ async def test_options_flow_arrival_time_step( assert entry.options == { CONF_ROUTE_MODE: ROUTE_MODE_FASTEST, CONF_ARRIVAL_TIME: "08:00:00", + CONF_TRAFFIC_MODE: True, } @@ -465,6 +483,7 @@ async def test_options_flow_departure_time_step( assert entry.options == { CONF_ROUTE_MODE: ROUTE_MODE_FASTEST, CONF_DEPARTURE_TIME: "08:00:00", + CONF_TRAFFIC_MODE: True, } @@ -481,4 +500,5 @@ async def test_options_flow_no_time_step( entry = hass.config_entries.async_entries(DOMAIN)[0] assert entry.options == { CONF_ROUTE_MODE: ROUTE_MODE_FASTEST, + CONF_TRAFFIC_MODE: True, } diff --git a/tests/components/here_travel_time/test_init.py b/tests/components/here_travel_time/test_init.py index ff09c7e6ae9..4dbddd46633 100644 --- a/tests/components/here_travel_time/test_init.py +++ b/tests/components/here_travel_time/test_init.py @@ -4,14 +4,19 @@ from datetime import datetime import pytest -from homeassistant.components.here_travel_time.config_flow import DEFAULT_OPTIONS +from homeassistant.components.here_travel_time.config_flow import ( + DEFAULT_OPTIONS, + HERETravelTimeConfigFlow, +) from homeassistant.components.here_travel_time.const import ( CONF_ARRIVAL_TIME, CONF_DEPARTURE_TIME, CONF_ROUTE_MODE, + CONF_TRAFFIC_MODE, DOMAIN, ROUTE_MODE_FASTEST, ) +from homeassistant.config_entries import ConfigEntryState from homeassistant.core import HomeAssistant from .const import DEFAULT_CONFIG @@ -44,9 +49,34 @@ async def test_unload_entry(hass: HomeAssistant, options) -> None: unique_id="0123456789", data=DEFAULT_CONFIG, options=options, + version=HERETravelTimeConfigFlow.VERSION, + minor_version=HERETravelTimeConfigFlow.MINOR_VERSION, ) entry.add_to_hass(hass) await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() assert await hass.config_entries.async_unload(entry.entry_id) + + +@pytest.mark.usefixtures("valid_response") +async def test_migrate_entry_v1_1_v1_2( + hass: HomeAssistant, +) -> None: + """Test successful migration of entry data.""" + mock_entry = MockConfigEntry( + domain=DOMAIN, + data=DEFAULT_CONFIG, + options=DEFAULT_OPTIONS, + version=1, + minor_version=1, + ) + mock_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_entry.entry_id) + await hass.async_block_till_done() + + updated_entry = hass.config_entries.async_get_entry(mock_entry.entry_id) + + assert updated_entry.state is ConfigEntryState.LOADED + assert updated_entry.minor_version == 2 + assert updated_entry.options[CONF_TRAFFIC_MODE] is True diff --git a/tests/components/here_travel_time/test_sensor.py b/tests/components/here_travel_time/test_sensor.py index 7c8946b7049..b96e77a6b6d 100644 --- a/tests/components/here_travel_time/test_sensor.py +++ b/tests/components/here_travel_time/test_sensor.py @@ -11,6 +11,7 @@ from here_routing import ( Return, RoutingMode, Spans, + TrafficMode, TransportMode, ) from here_transit import ( @@ -21,7 +22,10 @@ from here_transit import ( ) import pytest -from homeassistant.components.here_travel_time.config_flow import DEFAULT_OPTIONS +from homeassistant.components.here_travel_time.config_flow import ( + DEFAULT_OPTIONS, + HERETravelTimeConfigFlow, +) from homeassistant.components.here_travel_time.const import ( CONF_ARRIVAL_TIME, CONF_DEPARTURE_TIME, @@ -32,6 +36,7 @@ from homeassistant.components.here_travel_time.const import ( CONF_ORIGIN_LATITUDE, CONF_ORIGIN_LONGITUDE, CONF_ROUTE_MODE, + CONF_TRAFFIC_MODE, DEFAULT_SCAN_INTERVAL, DOMAIN, ICON_BICYCLE, @@ -85,29 +90,33 @@ from tests.common import ( @pytest.mark.parametrize( - ("mode", "icon", "arrival_time", "departure_time"), + ("mode", "icon", "traffic_mode", "arrival_time", "departure_time"), [ ( TRAVEL_MODE_CAR, ICON_CAR, + False, None, None, ), ( TRAVEL_MODE_BICYCLE, ICON_BICYCLE, + True, None, None, ), ( TRAVEL_MODE_PEDESTRIAN, ICON_PEDESTRIAN, + True, None, "08:00:00", ), ( TRAVEL_MODE_TRUCK, ICON_TRUCK, + True, None, "08:00:00", ), @@ -118,6 +127,7 @@ async def test_sensor( hass: HomeAssistant, mode, icon, + traffic_mode, arrival_time, departure_time, ) -> None: @@ -137,9 +147,12 @@ async def test_sensor( }, options={ CONF_ROUTE_MODE: ROUTE_MODE_FASTEST, + CONF_TRAFFIC_MODE: traffic_mode, CONF_ARRIVAL_TIME: arrival_time, CONF_DEPARTURE_TIME: departure_time, }, + version=HERETravelTimeConfigFlow.VERSION, + minor_version=HERETravelTimeConfigFlow.MINOR_VERSION, ) entry.add_to_hass(hass) await hass.config_entries.async_setup(entry.entry_id) @@ -197,6 +210,8 @@ async def test_circular_ref( CONF_NAME: "test", }, options=DEFAULT_OPTIONS, + version=HERETravelTimeConfigFlow.VERSION, + minor_version=HERETravelTimeConfigFlow.MINOR_VERSION, ) entry.add_to_hass(hass) await hass.config_entries.async_setup(entry.entry_id) @@ -228,7 +243,10 @@ async def test_public_transport(hass: HomeAssistant) -> None: CONF_ROUTE_MODE: ROUTE_MODE_FASTEST, CONF_ARRIVAL_TIME: "08:00:00", CONF_DEPARTURE_TIME: None, + CONF_TRAFFIC_MODE: True, }, + version=HERETravelTimeConfigFlow.VERSION, + minor_version=HERETravelTimeConfigFlow.MINOR_VERSION, ) entry.add_to_hass(hass) await hass.config_entries.async_setup(entry.entry_id) @@ -260,6 +278,8 @@ async def test_no_attribution_response(hass: HomeAssistant) -> None: CONF_NAME: "test", }, options=DEFAULT_OPTIONS, + version=HERETravelTimeConfigFlow.VERSION, + minor_version=HERETravelTimeConfigFlow.MINOR_VERSION, ) entry.add_to_hass(hass) await hass.config_entries.async_setup(entry.entry_id) @@ -307,6 +327,8 @@ async def test_entity_ids(hass: HomeAssistant, valid_response: MagicMock) -> Non CONF_NAME: "test", }, options=DEFAULT_OPTIONS, + version=HERETravelTimeConfigFlow.VERSION, + minor_version=HERETravelTimeConfigFlow.MINOR_VERSION, ) entry.add_to_hass(hass) await hass.config_entries.async_setup(entry.entry_id) @@ -324,6 +346,7 @@ async def test_entity_ids(hass: HomeAssistant, valid_response: MagicMock) -> Non routing_mode=RoutingMode.FAST, arrival_time=None, departure_time=None, + traffic_mode=TrafficMode.DEFAULT, return_values=[Return.POLYINE, Return.SUMMARY], spans=[Spans.NAMES], ) @@ -346,6 +369,8 @@ async def test_destination_entity_not_found( CONF_NAME: "test", }, options=DEFAULT_OPTIONS, + version=HERETravelTimeConfigFlow.VERSION, + minor_version=HERETravelTimeConfigFlow.MINOR_VERSION, ) entry.add_to_hass(hass) await hass.config_entries.async_setup(entry.entry_id) @@ -374,6 +399,8 @@ async def test_origin_entity_not_found( CONF_NAME: "test", }, options=DEFAULT_OPTIONS, + version=HERETravelTimeConfigFlow.VERSION, + minor_version=HERETravelTimeConfigFlow.MINOR_VERSION, ) entry.add_to_hass(hass) await hass.config_entries.async_setup(entry.entry_id) @@ -406,6 +433,8 @@ async def test_invalid_destination_entity_state( CONF_NAME: "test", }, options=DEFAULT_OPTIONS, + version=HERETravelTimeConfigFlow.VERSION, + minor_version=HERETravelTimeConfigFlow.MINOR_VERSION, ) entry.add_to_hass(hass) await hass.config_entries.async_setup(entry.entry_id) @@ -440,6 +469,8 @@ async def test_invalid_origin_entity_state( CONF_NAME: "test", }, options=DEFAULT_OPTIONS, + version=HERETravelTimeConfigFlow.VERSION, + minor_version=HERETravelTimeConfigFlow.MINOR_VERSION, ) entry.add_to_hass(hass) await hass.config_entries.async_setup(entry.entry_id) @@ -476,6 +507,8 @@ async def test_route_not_found( CONF_NAME: "test", }, options=DEFAULT_OPTIONS, + version=HERETravelTimeConfigFlow.VERSION, + minor_version=HERETravelTimeConfigFlow.MINOR_VERSION, ) entry.add_to_hass(hass) await hass.config_entries.async_setup(entry.entry_id) @@ -587,7 +620,12 @@ async def test_restore_state(hass: HomeAssistant) -> None: # create and add entry mock_entry = MockConfigEntry( - domain=DOMAIN, unique_id=DOMAIN, data=DEFAULT_CONFIG, options=DEFAULT_OPTIONS + domain=DOMAIN, + unique_id=DOMAIN, + data=DEFAULT_CONFIG, + options=DEFAULT_OPTIONS, + version=HERETravelTimeConfigFlow.VERSION, + minor_version=HERETravelTimeConfigFlow.MINOR_VERSION, ) mock_entry.add_to_hass(hass) @@ -656,6 +694,8 @@ async def test_transit_errors( CONF_NAME: "test", }, options=DEFAULT_OPTIONS, + version=HERETravelTimeConfigFlow.VERSION, + minor_version=HERETravelTimeConfigFlow.MINOR_VERSION, ) entry.add_to_hass(hass) await hass.config_entries.async_setup(entry.entry_id) @@ -682,6 +722,8 @@ async def test_routing_rate_limit( unique_id="0123456789", data=DEFAULT_CONFIG, options=DEFAULT_OPTIONS, + version=HERETravelTimeConfigFlow.VERSION, + minor_version=HERETravelTimeConfigFlow.MINOR_VERSION, ) entry.add_to_hass(hass) await hass.config_entries.async_setup(entry.entry_id) @@ -739,6 +781,8 @@ async def test_transit_rate_limit( CONF_NAME: "test", }, options=DEFAULT_OPTIONS, + version=HERETravelTimeConfigFlow.VERSION, + minor_version=HERETravelTimeConfigFlow.MINOR_VERSION, ) entry.add_to_hass(hass) await hass.config_entries.async_setup(entry.entry_id) @@ -791,6 +835,8 @@ async def test_multiple_sections( CONF_NAME: "test", }, options=DEFAULT_OPTIONS, + version=HERETravelTimeConfigFlow.VERSION, + minor_version=HERETravelTimeConfigFlow.MINOR_VERSION, ) entry.add_to_hass(hass) await hass.config_entries.async_setup(entry.entry_id)