import logging from typing import Any, Optional, Tuple import voluptuous as vol from homeassistant.core import callback, HomeAssistant from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv from homeassistant import config_entries from .api import InseeApi, AddressApi from .const import ( DOMAIN, CONF_LOCATION_MODE, LOCATION_MODES, HA_COORD, ZIP_CODE, CONF_INSEE_CODE, CONF_CODE_POSTAL, CONF_CITY, DEVICE_ID_KEY, ) from homeassistant.helpers.selector import LocationSelector from homeassistant import config_entries from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE from .api import InseeApi, AddressApi from .const import ( DOMAIN, CONF_LOCATION_MODE, LOCATION_MODES, HA_COORD, ZIP_CODE, CONF_INSEE_CODE, CONF_CODE_POSTAL, CONF_CITY, SELECT_COORD, CONF_LOCATION_MAP, ) _LOGGER = logging.getLogger(__name__) # Description of the config flow: # async_step_user is called when user starts to configure the integration # we follow with a flow of form/menu # eventually we call async_create_entry with a dictionnary of data # HA calls async_setup_entry with a ConfigEntry which wraps this data (defined in __init__.py) # in async_setup_entry we call hass.config_entries.async_forward_entry_setups to setup each relevant platform (sensor in our case) # HA calls async_setup_entry from sensor.py LOCATION_SCHEMA = vol.Schema( {vol.Required(CONF_LOCATION_MODE, default=HA_COORD): vol.In(LOCATION_MODES)} ) ZIPCODE_SCHEMA = vol.Schema({vol.Required(CONF_CODE_POSTAL, default=""): cv.string}) async def get_insee_code_fromzip(hass: HomeAssistant, data: dict) -> None: """Get Insee code from zip code""" session = async_get_clientsession(hass) try: client = InseeApi(session) return await client.get_data(data) except ValueError as exc: raise exc async def get_insee_code_fromcoord( hass: HomeAssistant, lat=None, lon=None ) -> Tuple[str, str, float, float]: """Get Insee code from GPS coords""" session = async_get_clientsession(hass) try: client = AddressApi(session) if lat is None or lon is None: lon = hass.config.as_dict()["longitude"] lat = hass.config.as_dict()["latitude"] return await client.get_data(lat, lon) except ValueError as exc: raise exc def _build_place_key(city) -> str: return f"{city['code']};{city['nom']};{city['centre']['coordinates'][0]};{city['centre']['coordinates'][1]}" class SetupConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): VERSION = 4 def __init__(self): """Initialize""" self.data = {} self.city_insee = [] @callback def _show_setup_form(self, step_id=None, user_input=None, schema=None, errors=None): """Show the setup form to the user.""" if user_input is None: user_input = {} return self.async_show_form( step_id=step_id, data_schema=schema, errors=errors or {}, ) async def async_step_user(self, user_input: Optional[dict[str, Any]] = None): """Called once with None as user_input, then a second time with user provided input""" errors = {} if user_input is not None: self.data[CONF_LOCATION_MODE] = user_input[CONF_LOCATION_MODE] if user_input[CONF_LOCATION_MODE] == HA_COORD: try: city_infos = await get_insee_code_fromcoord(self.hass) except ValueError: errors["base"] = "noinsee" if not errors: self.data[CONF_INSEE_CODE] = city_infos[0] self.data[CONF_CITY] = city_infos[1] self.data[CONF_LATITUDE] = city_infos[2] self.data[CONF_LONGITUDE] = city_infos[3] self.data[DEVICE_ID_KEY] = city_infos[0] return await self.async_step_location(user_input=self.data) elif user_input[CONF_LOCATION_MODE] == ZIP_CODE: self.data = user_input return await self.async_step_location() elif user_input[CONF_LOCATION_MODE] == SELECT_COORD: return await self.async_step_map_select() return self._show_setup_form("user", user_input, LOCATION_SCHEMA, errors) async def async_step_map_select(self, user_input=None): COORD_SCHEMA = vol.Schema( { vol.Required( CONF_LOCATION_MAP, default={ CONF_LATITUDE: self.hass.config.latitude, CONF_LONGITUDE: self.hass.config.longitude, }, ): LocationSelector() } ) errors = {} if user_input is not None: try: city_infos = await get_insee_code_fromcoord( self.hass, user_input[CONF_LOCATION_MAP][CONF_LATITUDE], user_input[CONF_LOCATION_MAP][CONF_LONGITUDE], ) except ValueError: errors["base"] = "noinsee" if not errors: self.data[CONF_INSEE_CODE] = city_infos[0] self.data[CONF_CITY] = city_infos[1] # TODO(kamaradclimber): it's not clear whether we should take lat/long from user input # or from address api results. self.data[CONF_LATITUDE] = city_infos[2] self.data[CONF_LONGITUDE] = city_infos[3] self.data[DEVICE_ID_KEY] = city_infos[0] return await self.async_step_location(user_input=self.data) return self._show_setup_form("map_select", None, COORD_SCHEMA, errors) async def async_step_location(self, user_input=None): """Handle location step""" errors = {} if user_input is not None: city_insee = user_input.get(CONF_INSEE_CODE) if not city_insee: # get INSEE Code try: self.city_insee = await get_insee_code_fromzip( self.hass, user_input[CONF_CODE_POSTAL] ) except ValueError: errors["base"] = "noinsee" if not errors: return await self.async_step_multilocation() else: return self._show_setup_form( "location", user_input, ZIPCODE_SCHEMA, errors ) return self.async_create_entry(title="vigieau", data=self.data) return self._show_setup_form("location", None, ZIPCODE_SCHEMA, errors) async def async_step_multilocation(self, user_input=None): """Handle location step""" errors = {} locations_for_form = {} for city in self.city_insee: locations_for_form[_build_place_key(city)] = f"{city['nom']}" if not user_input: if len(self.city_insee) > 1: return self.async_show_form( step_id="multilocation", data_schema=vol.Schema( { vol.Required("city", default=[]): vol.In( locations_for_form ), } ), errors=errors, ) user_input = {CONF_CITY: _build_place_key(self.city_insee[0])} city_infos = user_input[CONF_CITY].split(";") self.data[CONF_INSEE_CODE] = city_infos[0] self.data[CONF_CITY] = city_infos[1] self.data[CONF_LONGITUDE] = city_infos[2] self.data[CONF_LATITUDE] = city_infos[3] self.data[DEVICE_ID_KEY] = city_infos[0] return await self.async_step_location(self.data)