"""Config flow for AirNow integration.""" import logging from typing import Any from pyairnow import WebServiceAPI from pyairnow.errors import AirNowError, EmptyResponseError, InvalidKeyError import voluptuous as vol from homeassistant import core from homeassistant.config_entries import ( ConfigEntry, ConfigFlow, OptionsFlow, OptionsFlowWithConfigEntry, ) from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, CONF_RADIUS from homeassistant.core import HomeAssistant from homeassistant.data_entry_flow import FlowResult from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv from .const import DOMAIN _LOGGER = logging.getLogger(__name__) async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> bool: """Validate the user input allows us to connect. Data has the keys from DATA_SCHEMA with values provided by the user. """ session = async_get_clientsession(hass) client = WebServiceAPI(data[CONF_API_KEY], session=session) lat = data[CONF_LATITUDE] lng = data[CONF_LONGITUDE] distance = data[CONF_RADIUS] # Check that the provided latitude/longitude provide a response try: test_data = await client.observations.latLong(lat, lng, distance=distance) except InvalidKeyError as exc: raise InvalidAuth from exc except AirNowError as exc: raise CannotConnect from exc except EmptyResponseError as exc: raise InvalidLocation from exc if not test_data: raise InvalidLocation # Validation Succeeded return True class AirNowConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for AirNow.""" VERSION = 2 async def async_step_user( self, user_input: dict[str, Any] | None = None ) -> FlowResult: """Handle the initial step.""" errors = {} if user_input is not None: # Set a unique id based on latitude/longitude await self.async_set_unique_id( f"{user_input[CONF_LATITUDE]}-{user_input[CONF_LONGITUDE]}" ) self._abort_if_unique_id_configured() try: # Validate inputs await validate_input(self.hass, user_input) except CannotConnect: errors["base"] = "cannot_connect" except InvalidAuth: errors["base"] = "invalid_auth" except InvalidLocation: errors["base"] = "invalid_location" except Exception: # pylint: disable=broad-except _LOGGER.exception("Unexpected exception") errors["base"] = "unknown" else: # Create Entry radius = user_input.pop(CONF_RADIUS) return self.async_create_entry( title=( f"AirNow Sensor at {user_input[CONF_LATITUDE]}," f" {user_input[CONF_LONGITUDE]}" ), data=user_input, options={CONF_RADIUS: radius}, ) return self.async_show_form( step_id="user", data_schema=vol.Schema( { vol.Required(CONF_API_KEY): str, vol.Optional( CONF_LATITUDE, default=self.hass.config.latitude ): cv.latitude, vol.Optional( CONF_LONGITUDE, default=self.hass.config.longitude ): cv.longitude, vol.Optional(CONF_RADIUS, default=150): vol.All( int, vol.Range(min=5) ), } ), errors=errors, ) @staticmethod @core.callback def async_get_options_flow( config_entry: ConfigEntry, ) -> OptionsFlow: """Return the options flow.""" return AirNowOptionsFlowHandler(config_entry) class AirNowOptionsFlowHandler(OptionsFlowWithConfigEntry): """Handle an options flow for AirNow.""" async def async_step_init( self, user_input: dict[str, Any] | None = None ) -> FlowResult: """Manage the options.""" if user_input is not None: return self.async_create_entry(data=user_input) options_schema = vol.Schema( { vol.Optional(CONF_RADIUS): vol.All( int, vol.Range(min=5), ), } ) return self.async_show_form( step_id="init", data_schema=self.add_suggested_values_to_schema( options_schema, self.config_entry.options ), ) class CannotConnect(HomeAssistantError): """Error to indicate we cannot connect.""" class InvalidAuth(HomeAssistantError): """Error to indicate there is invalid auth.""" class InvalidLocation(HomeAssistantError): """Error to indicate the location is invalid."""