Home Assistant Config Flow & Discovery
SkillDev toolsPatterns for Home Assistant config flows, discovery handlers, options flows, and reauth for custom components
Instructions available. Your AI can read the instructions. Execution depends on the setup they require.
Account requirements not reviewed. Check the skill instructions before use; ahel provides instructions and does not run this skill.
Add ahel to your AI once: Claude, ChatGPT, Cursor, Claude Code or Codex. Then ask it to use this.
Then ask your AI: use the Home Assistant Config Flow & Discovery skill
What this skill tells your AI
The instructions your AI receives, as published by taurgis/has-marstek-local-api in .agents/skills/homeassistant-config-flow/SKILL.md and read by ahel’s review.
Use this skill when adding or adjusting setup flows, discovery handlers, options flows, or reauth for this integration.
When to Use
- Creating or refining
config_flow.py - Handling discovery sources (user, dhcp, zeroconf, integration discovery)
- Adding/updating options flow or reauth steps
- Ensuring duplicate prevention and IP-change handling are correct
Core Principles
- UI-first: No new YAML; all setup in the UI.
- Async-only: All I/O must be awaited; use executor only for blocking library work.
- One device, one entry: Abort duplicates; update existing entries when discovery reports a new host/IP.
- Stable identifiers: Use BLE-MAC (or WiFi MAC) for unique IDs; never pivot unique IDs on IP.
- Selectors > raw text: Prefer HA selectors for better UX and validation.
Step Patterns
async_step_user: show form whenuser_input is None; on submit, validate connectivity; return errors with keys (cannot_connect,invalid_auth,already_configured).async_step_dhcp/async_step_zeroconf/async_step_integration_discovery: deduplicate via MAC/unique ID; if existing entry with new host, update data and abort withalready_configured.async_step_confirm: for discovery flows, prefill known values and ask user to confirm.- Unicast
Marstek.GetDevice(manual IP/port, Confirm device, repairs) must callget_device_info(..., udp_client=get_udp_client(hass, bind_port_for_host(host, port))). Do not bind a second socket and do not pause for that probe. Pause pooled listeners only around broadcastdiscover_devices(). Pausing does not unbind; LinuxSO_REUSEPORTstill delivers the reply to the coordinator.
Reauth Flow Pattern
Reauth handles authentication/connection failures gracefully, allowing users to update credentials or IP addresses without removing the integration.
Implementation Steps
- Entry point:
async_step_reauth(entry_data)- Called when reauth is triggered - Confirm step:
async_step_reauth_confirm(user_input)- Show form, validate, update entry
Code Pattern
from homeassistant.config_entries import SOURCE_REAUTH
async def async_step_reauth(
self, entry_data: dict[str, Any]
) -> ConfigFlowResult:
"""Handle reauth when device becomes unreachable."""
return await self.async_step_reauth_confirm()
async def async_step_reauth_confirm(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Confirm reauth dialog."""
errors: dict[str, str] = {}
reauth_entry = self._get_reauth_entry()
if user_input is not None:
host = user_input.get(CONF_HOST)
try:
device_info = await get_device_info(
host=host, port=port, udp_client=udp_client
)
if device_info:
# Validate unique ID unchanged
await self.async_set_unique_id(device_info["ble_mac"])
self._abort_if_unique_id_mismatch()
# Preferred: use async_update_reload_and_abort helper
return self.async_update_reload_and_abort(
reauth_entry,
data_updates={CONF_HOST: host},
)
errors["base"] = "cannot_connect"
except (OSError, TimeoutError):
errors["base"] = "cannot_connect"
return self.async_show_form(
step_id="reauth_confirm",
data_schema=vol.Schema({vol.Required(CONF_HOST): cv.string}),
errors=errors,
description_placeholders={"host": reauth_entry.data.get(CONF_HOST, "")},
)
Key Helpers
self._get_reauth_entry()- Get the config entry being reauthenticatedself.async_update_reload_and_abort()- Update entry, reload, and abort withreauth_successful(preferred)self._abort_if_unique_id_mismatch()- Ensure the same device is being reauthenticated- Check source:
if self.source == SOURCE_REAUTH:
Triggering Reauth
From coordinator or setup when connection fails repeatedly:
entry.async_start_reauth(hass)
Required Translations
Add to strings.json and translations/en.json:
"reauth_confirm": {
"title": "Reconnect Device",
"description": "The device at {host} is not responding.",
"data": {"host": "IP address"}
}
And abort reason:
"abort": {
"reauth_successful": "Device reconnected successfully"
}
Form Patterns
Grouping Input Fields (sections)
Use section() to group related fields into collapsible sections:
from homeassistant.data_entry_flow import section
data_schema = {
vol.Required("host"): str,
vol.Required("advanced_options"): section(
vol.Schema({
vol.Optional("timeout", default=30): int,
vol.Optional("retry_count", default=3): int,
}),
{"collapsed": True}, # Initially collapsed
)
}
Pre-filling Forms with Suggested Values
from homeassistant.helpers.schema_config_entry_flow import add_suggested_values_to_schema
return self.async_show_form(
data_schema=add_suggested_values_to_schema(
OPTIONS_SCHEMA, self.config_entry.options
)
)
Browser Autofill
Use recognized keys (username, password) or TextSelector with autocomplete:
vol.Required(CONF_USERNAME): TextSelector(
TextSelectorConfig(type=TextSelectorType.EMAIL, autocomplete="username")
),
Read-only Fields in Options
For frozen configuration shown in options:
vol.Optional(CONF_DEVICE_ID): EntitySelector(
EntitySelectorConfig(read_only=True)
),
Navigation Menu
return self.async_show_menu(
step_id="user",
menu_options=["discovery", "manual"],
description_placeholders={"model": "Venus A"},
)
Long-running Tasks (Show Progress)
if not self.task:
self.task = self.hass.async_create_task(long_running_operation())
if not self.task.done():
return self.async_show_progress(
progress_action="connecting",
progress_task=self.task,
)
return self.async_show_progress_done(next_step_id="finish")
Options Flow
- Implement
async_get_options_flowto return anOptionsFlowHandler. - Store runtime preferences in
entry.options; keep credentials inentry.data. - Register
entry.add_update_listenerin__init__.pyto reload on options change. - Use selectors for numeric intervals, toggles, enums, and text fields where helpful.
- Use
add_suggested_values_to_schema()to pre-fill current options.
Discovery Handling
- Manifest-driven discovery (dhcp/zeroconf/ssdp) should land in dedicated steps.
- The scanner handles IP changes; do not add fallback discovery inside coordinator updates.
- On discovery, set unique ID early and call
self._abort_if_unique_id_configured(). - If the device is known but host changed, update the entry data and abort with
already_configured.
Error Keys & Translations
- Define user-facing errors in
strings.jsonand mirror totranslations/en.json. - Preferred keys:
cannot_connect,invalid_auth,already_configured,unknown. - Provide helpful
description_placeholderswhen useful (e.g.,{ip_address}).
Form Translation Structure
Config flow forms are translated via strings.json:
{
"config": {
"step": {
"user": {
"title": "Set up Marstek",
"description": "Connect to your {model} device.",
"data": {
"host": "Hostname or IP",
"port": "Port"
},
"data_description": {
"host": "The IP address of your Marstek device on the local network"
},
"sections": {
"advanced_options": {
"name": "Advanced Options",
"description": "Optional configuration"
}
}
}
},
"error": {
"cannot_connect": "Cannot connect to device",
"invalid_auth": "Invalid authentication"
},
"abort": {
"already_configured": "Device is already configured",
"reauth_successful": "Reauthentication successful"
}
}
}
Form translation keys:
title- Form titledescription- Form description (supports placeholders viadescription_placeholders)data- Field labels (keyed by field name)data_description- Field help text (shown below field)sections- Section names and descriptions (forsection()fields)
Validation Checklist
- Unique ID set from BLE-MAC/WiFi MAC; duplicates abort
- User step validates connectivity asynchronously
- Discovery steps update existing entry on host/IP change
- Options flow reloads entry on change
- Reauth asks only for the changed credential
- Selectors used where appropriate; translations updated
Signals
- GitHub stars
- 35
- Forks
- 5
- Last commit
- Sep 2026
Advanced
- Item type
- skill
- Key
homeassistant-config-flow- Source
- github.com/taurgis/has-marstek-local-api
github.com/taurgis/has-marstek-local-api