mirror of
https://github.com/home-assistant/core.git
synced 2026-04-06 23:47:33 +00:00
Environment Canada station selector (#154307)
Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
This commit is contained in:
@@ -6,11 +6,18 @@ import xml.etree.ElementTree as ET
|
||||
|
||||
import aiohttp
|
||||
from env_canada import ECWeather, ec_exc
|
||||
from env_canada.ec_weather import get_ec_sites_list
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
|
||||
from homeassistant.const import CONF_LANGUAGE, CONF_LATITUDE, CONF_LONGITUDE
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.selector import (
|
||||
SelectOptionDict,
|
||||
SelectSelector,
|
||||
SelectSelectorConfig,
|
||||
SelectSelectorMode,
|
||||
)
|
||||
|
||||
from .const import CONF_STATION, CONF_TITLE, DOMAIN
|
||||
|
||||
@@ -25,14 +32,16 @@ async def validate_input(data):
|
||||
lang = data.get(CONF_LANGUAGE).lower()
|
||||
|
||||
if station:
|
||||
# When station is provided, use it and get the coordinates from ECWeather
|
||||
weather_data = ECWeather(station_id=station, language=lang)
|
||||
else:
|
||||
weather_data = ECWeather(coordinates=(lat, lon), language=lang)
|
||||
await weather_data.update()
|
||||
|
||||
if lat is None or lon is None:
|
||||
await weather_data.update()
|
||||
# Always use the station's coordinates, not the user-provided ones
|
||||
lat = weather_data.lat
|
||||
lon = weather_data.lon
|
||||
else:
|
||||
# When no station is provided, use coordinates to find nearest station
|
||||
weather_data = ECWeather(coordinates=(lat, lon), language=lang)
|
||||
await weather_data.update()
|
||||
|
||||
return {
|
||||
CONF_TITLE: weather_data.metadata.location,
|
||||
@@ -46,6 +55,13 @@ class EnvironmentCanadaConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
"""Handle a config flow for Environment Canada weather."""
|
||||
|
||||
VERSION = 1
|
||||
_station_codes: list[dict[str, str]] | None = None
|
||||
|
||||
async def _get_station_codes(self) -> list[dict[str, str]]:
|
||||
"""Get station codes, cached after first call."""
|
||||
if self._station_codes is None:
|
||||
self._station_codes = await get_ec_sites_list()
|
||||
return self._station_codes
|
||||
|
||||
async def async_step_user(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
@@ -80,9 +96,21 @@ class EnvironmentCanadaConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
self._abort_if_unique_id_configured()
|
||||
return self.async_create_entry(title=info[CONF_TITLE], data=user_input)
|
||||
|
||||
station_codes = await self._get_station_codes()
|
||||
|
||||
data_schema = vol.Schema(
|
||||
{
|
||||
vol.Optional(CONF_STATION): str,
|
||||
vol.Optional(CONF_STATION): SelectSelector(
|
||||
SelectSelectorConfig(
|
||||
options=[
|
||||
SelectOptionDict(
|
||||
value=station["value"], label=station["label"]
|
||||
)
|
||||
for station in station_codes
|
||||
],
|
||||
mode=SelectSelectorMode.DROPDOWN,
|
||||
)
|
||||
),
|
||||
vol.Optional(
|
||||
CONF_LATITUDE, default=self.hass.config.latitude
|
||||
): cv.latitude,
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"title": "Environment Canada: Location and language",
|
||||
"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.",
|
||||
"title": "Environment Canada: weather location and language",
|
||||
"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.",
|
||||
"data": {
|
||||
"latitude": "[%key:common::config_flow::data::latitude%]",
|
||||
"longitude": "[%key:common::config_flow::data::longitude%]",
|
||||
"station": "Station code",
|
||||
"station": "Weather station",
|
||||
"language": "Weather information language"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,12 +15,17 @@ from homeassistant.data_entry_flow import FlowResultType
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
FAKE_CONFIG = {
|
||||
CONF_STATION: "ON/s1234567",
|
||||
CONF_STATION: "123",
|
||||
CONF_LANGUAGE: "English",
|
||||
CONF_LATITUDE: 42.42,
|
||||
CONF_LONGITUDE: -42.42,
|
||||
}
|
||||
FAKE_TITLE = "Universal title!"
|
||||
FAKE_STATIONS = [
|
||||
{"label": "Toronto, ON", "value": "123"},
|
||||
{"label": "Ottawa, ON", "value": "456"},
|
||||
{"label": "Montreal, QC", "value": "789"},
|
||||
]
|
||||
|
||||
|
||||
def mocked_ec():
|
||||
@@ -40,10 +45,19 @@ def mocked_ec():
|
||||
)
|
||||
|
||||
|
||||
def mocked_stations():
|
||||
"""Mock the station list."""
|
||||
return patch(
|
||||
"homeassistant.components.environment_canada.config_flow.get_ec_sites_list",
|
||||
return_value=FAKE_STATIONS,
|
||||
)
|
||||
|
||||
|
||||
async def test_create_entry(hass: HomeAssistant) -> None:
|
||||
"""Test creating an entry."""
|
||||
with (
|
||||
mocked_ec(),
|
||||
mocked_stations(),
|
||||
patch(
|
||||
"homeassistant.components.environment_canada.async_setup_entry",
|
||||
return_value=True,
|
||||
@@ -66,12 +80,13 @@ async def test_create_same_entry_twice(hass: HomeAssistant) -> None:
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
data=FAKE_CONFIG,
|
||||
unique_id="ON/s1234567-english",
|
||||
unique_id="123-english",
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
with (
|
||||
mocked_ec(),
|
||||
mocked_stations(),
|
||||
patch(
|
||||
"homeassistant.components.environment_canada.async_setup_entry",
|
||||
return_value=True,
|
||||
@@ -101,9 +116,12 @@ async def test_create_same_entry_twice(hass: HomeAssistant) -> None:
|
||||
async def test_exception_handling(hass: HomeAssistant, error) -> None:
|
||||
"""Test exception handling."""
|
||||
exc, base_error = error
|
||||
with patch(
|
||||
"homeassistant.components.environment_canada.config_flow.ECWeather",
|
||||
side_effect=exc,
|
||||
with (
|
||||
mocked_stations(),
|
||||
patch(
|
||||
"homeassistant.components.environment_canada.config_flow.ECWeather",
|
||||
side_effect=exc,
|
||||
),
|
||||
):
|
||||
flow = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
@@ -121,6 +139,7 @@ async def test_lat_lon_not_specified(hass: HomeAssistant) -> None:
|
||||
"""Test that the import step works when coordinates are not specified."""
|
||||
with (
|
||||
mocked_ec(),
|
||||
mocked_stations(),
|
||||
patch(
|
||||
"homeassistant.components.environment_canada.async_setup_entry",
|
||||
return_value=True,
|
||||
@@ -136,3 +155,31 @@ async def test_lat_lon_not_specified(hass: HomeAssistant) -> None:
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert result["data"] == FAKE_CONFIG
|
||||
assert result["title"] == FAKE_TITLE
|
||||
|
||||
|
||||
async def test_coordinates_without_station(hass: HomeAssistant) -> None:
|
||||
"""Test setup with coordinates but no station ID."""
|
||||
with (
|
||||
mocked_ec(),
|
||||
mocked_stations(),
|
||||
patch(
|
||||
"homeassistant.components.environment_canada.async_setup_entry",
|
||||
return_value=True,
|
||||
),
|
||||
):
|
||||
# Config with coordinates but no station
|
||||
config_no_station = {
|
||||
CONF_LANGUAGE: "English",
|
||||
CONF_LATITUDE: 42.42,
|
||||
CONF_LONGITUDE: -42.42,
|
||||
}
|
||||
flow = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
flow["flow_id"], config_no_station
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert result["data"] == FAKE_CONFIG
|
||||
assert result["title"] == FAKE_TITLE
|
||||
|
||||
Reference in New Issue
Block a user