Skip to content

Commit 4ca1ae6

Browse files
michaeldavieclaudejoostlek
authored
Environment Canada station selector (home-assistant#154307)
Co-authored-by: Claude <[email protected]> Co-authored-by: Joost Lekkerkerker <[email protected]>
1 parent 3d130a9 commit 4ca1ae6

File tree

3 files changed

+89
-14
lines changed

3 files changed

+89
-14
lines changed

homeassistant/components/environment_canada/config_flow.py

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,18 @@
66

77
import aiohttp
88
from env_canada import ECWeather, ec_exc
9+
from env_canada.ec_weather import get_ec_sites_list
910
import voluptuous as vol
1011

1112
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
1213
from homeassistant.const import CONF_LANGUAGE, CONF_LATITUDE, CONF_LONGITUDE
1314
from homeassistant.helpers import config_validation as cv
15+
from homeassistant.helpers.selector import (
16+
SelectOptionDict,
17+
SelectSelector,
18+
SelectSelectorConfig,
19+
SelectSelectorMode,
20+
)
1421

1522
from .const import CONF_STATION, CONF_TITLE, DOMAIN
1623

@@ -25,14 +32,16 @@ async def validate_input(data):
2532
lang = data.get(CONF_LANGUAGE).lower()
2633

2734
if station:
35+
# When station is provided, use it and get the coordinates from ECWeather
2836
weather_data = ECWeather(station_id=station, language=lang)
29-
else:
30-
weather_data = ECWeather(coordinates=(lat, lon), language=lang)
31-
await weather_data.update()
32-
33-
if lat is None or lon is None:
37+
await weather_data.update()
38+
# Always use the station's coordinates, not the user-provided ones
3439
lat = weather_data.lat
3540
lon = weather_data.lon
41+
else:
42+
# When no station is provided, use coordinates to find nearest station
43+
weather_data = ECWeather(coordinates=(lat, lon), language=lang)
44+
await weather_data.update()
3645

3746
return {
3847
CONF_TITLE: weather_data.metadata.location,
@@ -46,6 +55,13 @@ class EnvironmentCanadaConfigFlow(ConfigFlow, domain=DOMAIN):
4655
"""Handle a config flow for Environment Canada weather."""
4756

4857
VERSION = 1
58+
_station_codes: list[dict[str, str]] | None = None
59+
60+
async def _get_station_codes(self) -> list[dict[str, str]]:
61+
"""Get station codes, cached after first call."""
62+
if self._station_codes is None:
63+
self._station_codes = await get_ec_sites_list()
64+
return self._station_codes
4965

5066
async def async_step_user(
5167
self, user_input: dict[str, Any] | None = None
@@ -80,9 +96,21 @@ async def async_step_user(
8096
self._abort_if_unique_id_configured()
8197
return self.async_create_entry(title=info[CONF_TITLE], data=user_input)
8298

99+
station_codes = await self._get_station_codes()
100+
83101
data_schema = vol.Schema(
84102
{
85-
vol.Optional(CONF_STATION): str,
103+
vol.Optional(CONF_STATION): SelectSelector(
104+
SelectSelectorConfig(
105+
options=[
106+
SelectOptionDict(
107+
value=station["value"], label=station["label"]
108+
)
109+
for station in station_codes
110+
],
111+
mode=SelectSelectorMode.DROPDOWN,
112+
)
113+
),
86114
vol.Optional(
87115
CONF_LATITUDE, default=self.hass.config.latitude
88116
): cv.latitude,

homeassistant/components/environment_canada/strings.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22
"config": {
33
"step": {
44
"user": {
5-
"title": "Environment Canada: Location and language",
6-
"description": "You can specify a location using either a station code or latitude/longitude coordinates.\n\nDefault behavior: If no station code is entered, the system uses the latitude/longitude values configured in your Home Assistant installation.\n\nStation code format: Station codes follow the format \"s0000123\" or simply \"123\".\n\nFind station codes at https://dd.weather.gc.ca/today/citypage_weather/docs/site_list_towns_en.csv.",
5+
"title": "Environment Canada: weather location and language",
6+
"description": "Select a weather station from the dropdown, or specify coordinates to use the closest station. The default coordinates are from your Home Assistant installation. Weather information can be retrieved in English or French.",
77
"data": {
88
"latitude": "[%key:common::config_flow::data::latitude%]",
99
"longitude": "[%key:common::config_flow::data::longitude%]",
10-
"station": "Station code",
10+
"station": "Weather station",
1111
"language": "Weather information language"
1212
}
1313
}

tests/components/environment_canada/test_config_flow.py

Lines changed: 52 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,17 @@
1515
from tests.common import MockConfigEntry
1616

1717
FAKE_CONFIG = {
18-
CONF_STATION: "ON/s1234567",
18+
CONF_STATION: "123",
1919
CONF_LANGUAGE: "English",
2020
CONF_LATITUDE: 42.42,
2121
CONF_LONGITUDE: -42.42,
2222
}
2323
FAKE_TITLE = "Universal title!"
24+
FAKE_STATIONS = [
25+
{"label": "Toronto, ON", "value": "123"},
26+
{"label": "Ottawa, ON", "value": "456"},
27+
{"label": "Montreal, QC", "value": "789"},
28+
]
2429

2530

2631
def mocked_ec():
@@ -40,10 +45,19 @@ def mocked_ec():
4045
)
4146

4247

48+
def mocked_stations():
49+
"""Mock the station list."""
50+
return patch(
51+
"homeassistant.components.environment_canada.config_flow.get_ec_sites_list",
52+
return_value=FAKE_STATIONS,
53+
)
54+
55+
4356
async def test_create_entry(hass: HomeAssistant) -> None:
4457
"""Test creating an entry."""
4558
with (
4659
mocked_ec(),
60+
mocked_stations(),
4761
patch(
4862
"homeassistant.components.environment_canada.async_setup_entry",
4963
return_value=True,
@@ -66,12 +80,13 @@ async def test_create_same_entry_twice(hass: HomeAssistant) -> None:
6680
entry = MockConfigEntry(
6781
domain=DOMAIN,
6882
data=FAKE_CONFIG,
69-
unique_id="ON/s1234567-english",
83+
unique_id="123-english",
7084
)
7185
entry.add_to_hass(hass)
7286

7387
with (
7488
mocked_ec(),
89+
mocked_stations(),
7590
patch(
7691
"homeassistant.components.environment_canada.async_setup_entry",
7792
return_value=True,
@@ -101,9 +116,12 @@ async def test_create_same_entry_twice(hass: HomeAssistant) -> None:
101116
async def test_exception_handling(hass: HomeAssistant, error) -> None:
102117
"""Test exception handling."""
103118
exc, base_error = error
104-
with patch(
105-
"homeassistant.components.environment_canada.config_flow.ECWeather",
106-
side_effect=exc,
119+
with (
120+
mocked_stations(),
121+
patch(
122+
"homeassistant.components.environment_canada.config_flow.ECWeather",
123+
side_effect=exc,
124+
),
107125
):
108126
flow = await hass.config_entries.flow.async_init(
109127
DOMAIN, context={"source": config_entries.SOURCE_USER}
@@ -121,6 +139,7 @@ async def test_lat_lon_not_specified(hass: HomeAssistant) -> None:
121139
"""Test that the import step works when coordinates are not specified."""
122140
with (
123141
mocked_ec(),
142+
mocked_stations(),
124143
patch(
125144
"homeassistant.components.environment_canada.async_setup_entry",
126145
return_value=True,
@@ -136,3 +155,31 @@ async def test_lat_lon_not_specified(hass: HomeAssistant) -> None:
136155
assert result["type"] is FlowResultType.CREATE_ENTRY
137156
assert result["data"] == FAKE_CONFIG
138157
assert result["title"] == FAKE_TITLE
158+
159+
160+
async def test_coordinates_without_station(hass: HomeAssistant) -> None:
161+
"""Test setup with coordinates but no station ID."""
162+
with (
163+
mocked_ec(),
164+
mocked_stations(),
165+
patch(
166+
"homeassistant.components.environment_canada.async_setup_entry",
167+
return_value=True,
168+
),
169+
):
170+
# Config with coordinates but no station
171+
config_no_station = {
172+
CONF_LANGUAGE: "English",
173+
CONF_LATITUDE: 42.42,
174+
CONF_LONGITUDE: -42.42,
175+
}
176+
flow = await hass.config_entries.flow.async_init(
177+
DOMAIN, context={"source": config_entries.SOURCE_USER}
178+
)
179+
result = await hass.config_entries.flow.async_configure(
180+
flow["flow_id"], config_no_station
181+
)
182+
await hass.async_block_till_done()
183+
assert result["type"] is FlowResultType.CREATE_ENTRY
184+
assert result["data"] == FAKE_CONFIG
185+
assert result["title"] == FAKE_TITLE

0 commit comments

Comments
 (0)