diff --git a/.coveragerc b/.coveragerc index beb7719db44..6d042b3f46e 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1136,6 +1136,7 @@ omit = homeassistant/components/tradfri/sensor.py homeassistant/components/tradfri/switch.py homeassistant/components/trafikverket_train/sensor.py + homeassistant/components/trafikverket_weatherstation/__init__.py homeassistant/components/trafikverket_weatherstation/sensor.py homeassistant/components/transmission/sensor.py homeassistant/components/transmission/switch.py diff --git a/homeassistant/components/trafikverket_weatherstation/__init__.py b/homeassistant/components/trafikverket_weatherstation/__init__.py index 7feac4aad27..535ef304557 100644 --- a/homeassistant/components/trafikverket_weatherstation/__init__.py +++ b/homeassistant/components/trafikverket_weatherstation/__init__.py @@ -1 +1,39 @@ """The trafikverket_weatherstation component.""" +from __future__ import annotations + +import logging + +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant + +from .const import DOMAIN, PLATFORMS + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Set up Trafikverket Weatherstation from a config entry.""" + hass.data.setdefault(DOMAIN, {}) + title = entry.title + + hass.config_entries.async_setup_platforms(entry, PLATFORMS) + + _LOGGER.debug("Loaded entry for %s", title) + + return True + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Unload Trafikverket Weatherstation config entry.""" + + unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) + + title = entry.title + if unload_ok: + del hass.data[DOMAIN][entry.entry_id] + if not hass.data[DOMAIN]: + del hass.data[DOMAIN] + _LOGGER.debug("Unloaded entry for %s", title) + return unload_ok + + return False diff --git a/homeassistant/components/trafikverket_weatherstation/config_flow.py b/homeassistant/components/trafikverket_weatherstation/config_flow.py new file mode 100644 index 00000000000..44b8b124264 --- /dev/null +++ b/homeassistant/components/trafikverket_weatherstation/config_flow.py @@ -0,0 +1,70 @@ +"""Adds config flow for Trafikverket Weather integration.""" +from __future__ import annotations + +import voluptuous as vol + +from homeassistant import config_entries +from homeassistant.const import CONF_API_KEY, CONF_MONITORED_CONDITIONS, CONF_NAME +import homeassistant.helpers.config_validation as cv + +from .const import CONF_STATION, DOMAIN +from .sensor import SENSOR_TYPES + +SENSOR_LIST: dict[str, str | None] = { + description.key: description.name for (description) in SENSOR_TYPES +} + +DATA_SCHEMA = vol.Schema( + { + vol.Required(CONF_NAME): cv.string, + vol.Required(CONF_API_KEY): cv.string, + vol.Required(CONF_STATION): cv.string, + vol.Required(CONF_MONITORED_CONDITIONS, default=[]): cv.multi_select( + SENSOR_LIST + ), + } +) + + +class TVWeatherConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + """Handle a config flow for Trafikverket Weatherstation integration.""" + + VERSION = 1 + + entry: config_entries.ConfigEntry + + async def async_step_import(self, config: dict): + """Import a configuration from config.yaml.""" + + self.context.update( + {"title_placeholders": {CONF_NAME: f"YAML import {DOMAIN}"}} + ) + + self._async_abort_entries_match({CONF_NAME: config[CONF_NAME]}) + return await self.async_step_user(user_input=config) + + async def async_step_user(self, user_input=None): + """Handle the initial step.""" + errors = {} + + if user_input is not None: + name = user_input[CONF_NAME] + api_key = user_input[CONF_API_KEY] + station = user_input[CONF_STATION] + conditions = user_input[CONF_MONITORED_CONDITIONS] + + return self.async_create_entry( + title=name, + data={ + CONF_NAME: name, + CONF_API_KEY: api_key, + CONF_STATION: station, + CONF_MONITORED_CONDITIONS: conditions, + }, + ) + + return self.async_show_form( + step_id="user", + data_schema=DATA_SCHEMA, + errors=errors, + ) diff --git a/homeassistant/components/trafikverket_weatherstation/const.py b/homeassistant/components/trafikverket_weatherstation/const.py new file mode 100644 index 00000000000..bfda5782084 --- /dev/null +++ b/homeassistant/components/trafikverket_weatherstation/const.py @@ -0,0 +1,8 @@ +"""Adds constants for Trafikverket Weather integration.""" + +DOMAIN = "trafikverket_weatherstation" +CONF_STATION = "station" +PLATFORMS = ["sensor"] +ATTRIBUTION = "Data provided by Trafikverket" +ATTR_MEASURE_TIME = "measure_time" +ATTR_ACTIVE = "active" diff --git a/homeassistant/components/trafikverket_weatherstation/manifest.json b/homeassistant/components/trafikverket_weatherstation/manifest.json index 6e123983e8b..202d2683d2d 100644 --- a/homeassistant/components/trafikverket_weatherstation/manifest.json +++ b/homeassistant/components/trafikverket_weatherstation/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/trafikverket_weatherstation", "requirements": ["pytrafikverket==0.1.6.2"], "codeowners": ["@endor-force"], + "config_flow": true, "iot_class": "cloud_polling" } diff --git a/homeassistant/components/trafikverket_weatherstation/sensor.py b/homeassistant/components/trafikverket_weatherstation/sensor.py index 5fe3c462a56..32703aef4cc 100644 --- a/homeassistant/components/trafikverket_weatherstation/sensor.py +++ b/homeassistant/components/trafikverket_weatherstation/sensor.py @@ -15,6 +15,7 @@ from homeassistant.components.sensor import ( SensorEntity, SensorEntityDescription, ) +from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import ( ATTR_ATTRIBUTION, CONF_API_KEY, @@ -28,18 +29,20 @@ from homeassistant.const import ( SPEED_METERS_PER_SECOND, TEMP_CELSIUS, ) +from homeassistant.core import HomeAssistant from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.entity_platform import ( + AddEntitiesCallback, + ConfigType, + DiscoveryInfoType, +) from homeassistant.util import Throttle +from .const import ATTR_ACTIVE, ATTR_MEASURE_TIME, ATTRIBUTION, CONF_STATION, DOMAIN + _LOGGER = logging.getLogger(__name__) -ATTRIBUTION = "Data provided by Trafikverket" -ATTR_MEASURE_TIME = "measure_time" -ATTR_ACTIVE = "active" - -CONF_STATION = "station" - MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=10) SCAN_INTERVAL = timedelta(seconds=300) @@ -144,18 +147,40 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( ) -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): - """Set up the Trafikverket sensor platform.""" +async def async_setup_platform( + hass: HomeAssistant, + config: ConfigType, + async_add_entities: AddEntitiesCallback, + discovery_info: DiscoveryInfoType = None, +) -> None: + """Import Trafikverket Weather configuration from YAML.""" + _LOGGER.warning( + # Config flow added in Home Assistant Core 2021.12, remove import flow in 2022.4 + "Loading Trafikverket Weather via platform setup is deprecated; Please remove it from your configuration" + ) + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_IMPORT}, + data=config, + ) + ) - sensor_name = config[CONF_NAME] - sensor_api = config[CONF_API_KEY] - sensor_station = config[CONF_STATION] + +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: + """Set up the Trafikverket sensor entry.""" + + sensor_name = entry.data[CONF_NAME] + sensor_api = entry.data[CONF_API_KEY] + sensor_station = entry.data[CONF_STATION] web_session = async_get_clientsession(hass) weather_api = TrafikverketWeather(web_session, sensor_api) - monitored_conditions = config[CONF_MONITORED_CONDITIONS] + monitored_conditions = entry.data[CONF_MONITORED_CONDITIONS] entities = [ TrafikverketWeatherStation( weather_api, sensor_name, sensor_station, description diff --git a/homeassistant/components/trafikverket_weatherstation/strings.json b/homeassistant/components/trafikverket_weatherstation/strings.json new file mode 100644 index 00000000000..7fa8071e389 --- /dev/null +++ b/homeassistant/components/trafikverket_weatherstation/strings.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "already_configured": "[%key:common::config_flow::abort::already_configured_account%]" + }, + "step": { + "user": { + "data": { + "name": "[%key:common::config_flow::data::username%]", + "api_key": "[%key:common::config_flow::data::api_key%]", + "station": "Station", + "conditions": "Monitored conditions" + } + } + } + } + } \ No newline at end of file diff --git a/homeassistant/components/trafikverket_weatherstation/translations/en.json b/homeassistant/components/trafikverket_weatherstation/translations/en.json new file mode 100644 index 00000000000..fd32bb80ab3 --- /dev/null +++ b/homeassistant/components/trafikverket_weatherstation/translations/en.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "already_configured": "Account is already configured" + }, + "step": { + "user": { + "data": { + "name": "Name", + "api_key": "API Key", + "station": "Station", + "conditions": "Monitored conditions" + } + } + } + } + } \ No newline at end of file diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index b8ba8c4aade..79e65b96234 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -307,6 +307,7 @@ FLOWS = [ "traccar", "tractive", "tradfri", + "trafikverket_weatherstation", "transmission", "tuya", "twentemilieu", diff --git a/requirements_test_all.txt b/requirements_test_all.txt index cc1b51bddb2..26a2823334c 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1180,6 +1180,10 @@ pytraccar==0.10.0 # homeassistant.components.tradfri pytradfri[async]==7.2.0 +# homeassistant.components.trafikverket_train +# homeassistant.components.trafikverket_weatherstation +pytrafikverket==0.1.6.2 + # homeassistant.components.usb pyudev==0.22.0 diff --git a/tests/components/trafikverket_weatherstation/__init__.py b/tests/components/trafikverket_weatherstation/__init__.py new file mode 100644 index 00000000000..836ee919195 --- /dev/null +++ b/tests/components/trafikverket_weatherstation/__init__.py @@ -0,0 +1 @@ +"""Tests for the Trafikverket weatherstation integration.""" diff --git a/tests/components/trafikverket_weatherstation/test_config_flow.py b/tests/components/trafikverket_weatherstation/test_config_flow.py new file mode 100644 index 00000000000..f46dc73e031 --- /dev/null +++ b/tests/components/trafikverket_weatherstation/test_config_flow.py @@ -0,0 +1,76 @@ +"""Test the Trafikverket weatherstation config flow.""" +from __future__ import annotations + +from unittest.mock import patch + +from homeassistant import config_entries +from homeassistant.const import CONF_API_KEY, CONF_MONITORED_CONDITIONS, CONF_NAME +from homeassistant.core import HomeAssistant + +DOMAIN = "trafikverket_weatherstation" +CONF_STATION = "station" + + +async def test_form(hass: HomeAssistant) -> None: + """Test we get the form.""" + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == "form" + assert result["errors"] == {} + + with patch( + "homeassistant.components.trafikverket_weatherstation.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_NAME: "Vallby Vasteras", + CONF_API_KEY: "1234567890", + CONF_STATION: "Vallby", + CONF_MONITORED_CONDITIONS: ["air_temp", "road_temp"], + }, + ) + await hass.async_block_till_done() + + assert result2["type"] == "create_entry" + assert result2["title"] == "Vallby Vasteras" + assert result2["data"] == { + "name": "Vallby Vasteras", + "api_key": "1234567890", + "station": "Vallby", + "monitored_conditions": ["air_temp", "road_temp"], + } + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_import_flow_success(hass: HomeAssistant) -> None: + """Test a successful import of yaml.""" + + with patch( + "homeassistant.components.trafikverket_weatherstation.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result2 = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data={ + CONF_NAME: "Vallby Vasteras", + CONF_API_KEY: "1234567890", + CONF_STATION: "Vallby", + CONF_MONITORED_CONDITIONS: ["air_temp", "road_temp"], + }, + ) + await hass.async_block_till_done() + + assert result2["type"] == "create_entry" + assert result2["title"] == "Vallby Vasteras" + assert result2["data"] == { + "name": "Vallby Vasteras", + "api_key": "1234567890", + "station": "Vallby", + "monitored_conditions": ["air_temp", "road_temp"], + } + assert len(mock_setup_entry.mock_calls) == 1