Repository: home-assistant/core
File: homeassistant/components/control4/config_flow.py (Python)

Code before:
    CONF_PASSWORD,
    CONF_SCAN_INTERVAL,
    CONF_USERNAME,
)
from homeassistant.core import callback
from homeassistant.helpers import aiohttp_client, config_validation as cv
from homeassistant.helpers.device_registry import format_mac

from . import Control4ConfigEntry
from .const import (
    CONF_CONTROLLER_UNIQUE_ID,
    DEFAULT_SCAN_INTERVAL,
    DOMAIN,
    MIN_SCAN_INTERVAL,
)

DATA_SCHEMA = vol.Schema(
    {
        vol.Required(CONF_HOST): str,
        vol.Required(CONF_USERNAME): str,
        vol.Required(CONF_PASSWORD): str,
    }
)


def _extract_error_message(err: Exception) -> str:
    """Extract a clean error message from a pyControl4 exception.

    The pyControl4 library returns errors as JSON strings like:
    {"C4ErrorResponse": {"code":401,"message":"Permission denied",...}}

    This function extracts just the human-readable message.
    """
    error_str = str(err)
    try:
        error_data = json.loads(error_str)
        if "C4ErrorResponse" in error_data:
            return error_data["C4ErrorResponse"].get("message", error_str)
    except (json.JSONDecodeError, TypeError, KeyError):
        pass
    return error_str


class Control4ConfigFlow(ConfigFlow, domain=DOMAIN):
    """Handle a config flow for Control4."""

    VERSION = 1

    async def _async_try_connect(
        self, user_input: dict[str, Any]
    ) -> tuple[dict[str, str], dict[str, Any] | None, dict[str, str]]:

Review comment (bug):
I think you went a bit over the top with this one 😅 This complicates it a bit too much, it would’ve just been fine to match the correct UI strings with the correct errors. Because now you are mixing translated and untranslated strings in the UI, which we shouldn’t do or you get something like „Keine Steuereinheit gefunden: something went wrong“ in a German UI which is weird. The translated part should be enough to identify the error and if we don’t know the error source, we can additionally log it

Code after:
    CONF_PASSWORD,
    CONF_SCAN_INTERVAL,
    CONF_USERNAME,
)
from homeassistant.core import callback
from homeassistant.helpers import aiohttp_client, config_validation as cv
from homeassistant.helpers.device_registry import format_mac

from . import Control4ConfigEntry
from .const import (
    CONF_CONTROLLER_UNIQUE_ID,
    DEFAULT_SCAN_INTERVAL,
    DOMAIN,
    MIN_SCAN_INTERVAL,
)

_LOGGER = logging.getLogger(__name__)

DATA_SCHEMA = vol.Schema(
    {
        vol.Required(CONF_HOST): str,
        vol.Required(CONF_USERNAME): str,
        vol.Required(CONF_PASSWORD): str,
    }
)


class Control4ConfigFlow(ConfigFlow, domain=DOMAIN):
    """Handle a config flow for Control4."""

    VERSION = 1

    async def _async_try_connect(
        self, user_input: dict[str, Any]
    ) -> tuple[dict[str, str], dict[str, Any] | None, dict[str, str]]:
        """Try to connect to Control4 and return errors, data, and placeholders."""
        errors: dict[str, str] = {}
        description_placeholders: dict[str, str] = {}
        data: dict[str, Any] | None = None

        host = user_input[CONF_HOST]
        username = user_input[CONF_USERNAME]
        password = user_input[CONF_PASSWORD]

        # Step 1: Authenticate with Control4 cloud API
        account_session = aiohttp_client.async_get_clientsession(self.hass)
        account = C4Account(username, password, account_session)
        try:
            await account.getAccountBearerToken()

            account_controllers = await account.getAccountControllers()