"""Config flow to configure the WLED integration.""" from typing import Any, override import voluptuous as vol from wled import WLED, Device, WLEDConnectionError, WLEDUnsupportedVersionError import yarl from homeassistant.components import onboarding from homeassistant.config_entries import ( SOURCE_RECONFIGURE, ConfigFlow, ConfigFlowResult, OptionsFlowWithReload, ) from homeassistant.const import CONF_HOST, CONF_MAC from homeassistant.core import callback from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.device_registry import format_mac from homeassistant.helpers.service_info.zeroconf import ZeroconfServiceInfo from .const import CONF_KEEP_MAIN_LIGHT, DEFAULT_KEEP_MAIN_LIGHT, DOMAIN from .coordinator import WLEDConfigEntry, normalize_mac_address def _normalize_host(host: str) -> str: """Normalize host by extracting hostname if a URL is provided.""" try: return yarl.URL(host).host or host except ValueError: pass return host class WLEDFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a WLED config flow.""" VERSION = 1 MINOR_VERSION = 2 discovered_host: str discovered_device: Device @staticmethod @callback @override def async_get_options_flow( config_entry: WLEDConfigEntry, ) -> WLEDOptionsFlowHandler: """Get the options flow for this handler.""" return WLEDOptionsFlowHandler() @override async def async_step_user( self, user_input: dict[str, Any] | None = None ) -> ConfigFlowResult: """Handle a flow initiated by the user.""" errors = {} if user_input is not None: host = _normalize_host(user_input[CONF_HOST]) try: device = await self._async_get_device(host) except WLEDUnsupportedVersionError: errors["base"] = "unsupported_version" except WLEDConnectionError: errors["base"] = "cannot_connect" else: mac_address = normalize_mac_address(device.info.mac_address) await self.async_set_unique_id(mac_address, raise_on_progress=False) if self.source == SOURCE_RECONFIGURE: entry = self._get_reconfigure_entry() self._abort_if_unique_id_mismatch( reason="unique_id_mismatch", description_placeholders={ "expected_mac": format_mac(entry.unique_id).upper(), "actual_mac": mac_address.upper(), }, ) return self.async_update_reload_and_abort( entry, data_updates={CONF_HOST: host}, ) self._abort_if_unique_id_configured(updates={CONF_HOST: host}) return self.async_create_entry( title=device.info.name, data={CONF_HOST: host}, ) data_schema = vol.Schema({vol.Required(CONF_HOST): str}) if self.source == SOURCE_RECONFIGURE: entry = self._get_reconfigure_entry() data_schema = self.add_suggested_values_to_schema( data_schema, entry.data, ) return self.async_show_form( step_id="user", data_schema=data_schema, errors=errors or {}, ) async def async_step_reconfigure( self, user_input: dict[str, Any] | None = None ) -> ConfigFlowResult: """Handle reconfigure flow for WLED entry.""" return await self.async_step_user(user_input) @override async def async_step_zeroconf( self, discovery_info: ZeroconfServiceInfo ) -> ConfigFlowResult: """Handle zeroconf discovery.""" # Abort quick if the mac address is provided by discovery info if mac := discovery_info.properties.get(CONF_MAC): await self.async_set_unique_id(normalize_mac_address(mac)) self._abort_if_unique_id_configured( updates={CONF_HOST: discovery_info.host} ) self.discovered_host = discovery_info.host try: self.discovered_device = await self._async_get_device(discovery_info.host) except WLEDUnsupportedVersionError: return self.async_abort(reason="unsupported_version") except WLEDConnectionError: return self.async_abort(reason="cannot_connect") device_mac_address = normalize_mac_address( self.discovered_device.info.mac_address ) await self.async_set_unique_id(device_mac_address) self._abort_if_unique_id_configured(updates={CONF_HOST: discovery_info.host}) self.context.update( { "title_placeholders": {"name": self.discovered_device.info.name}, "configuration_url": f"http://{discovery_info.host}", } ) return await self.async_step_zeroconf_confirm() async def async_step_zeroconf_confirm( self, user_input: dict[str, Any] | None = None ) -> ConfigFlowResult: """Handle a flow initiated by zeroconf.""" if user_input is not None or not onboarding.async_is_onboarded(self.hass): return self.async_create_entry( title=self.discovered_device.info.name, data={ CONF_HOST: self.discovered_host, }, ) return self.async_show_form( step_id="zeroconf_confirm", description_placeholders={"name": self.discovered_device.info.name}, ) async def _async_get_device(self, host: str) -> Device: """Get device information from WLED device.""" session = async_get_clientsession(self.hass) wled = WLED(host, session=session) return await wled.update() class WLEDOptionsFlowHandler(OptionsFlowWithReload): """Handle WLED options.""" async def async_step_init( self, user_input: dict[str, Any] | None = None ) -> ConfigFlowResult: """Manage WLED options.""" if user_input is not None: return self.async_create_entry(title="", data=user_input) return self.async_show_form( step_id="init", data_schema=vol.Schema( { vol.Optional( CONF_KEEP_MAIN_LIGHT, default=self.config_entry.options.get( CONF_KEEP_MAIN_LIGHT, DEFAULT_KEEP_MAIN_LIGHT ), ): bool, } ), )