Configuration Flow Development¶
Guide to HAEO's ConfigSubentry-based configuration flow implementation.
Overview¶
HAEO uses Home Assistant's ConfigSubentry architecture where each element is managed as a subentry:
- Hub flow (in
custom_components/haeo/flows/hub.py): Creates main hub entry with optimization settings - Element flows (in
custom_components/haeo/flows/element.py): Creates element ConfigSubentries usingConfigSubentryFlow - Network subentry: Automatically created representing the optimization network itself
This architecture follows Home Assistant's native subentry pattern. Elements appear as subentries under the main hub in the UI with proper parent-child management.
For general information on config flows, see the Home Assistant Config Flow documentation and Data Entry Flow.
Architecture Benefits¶
- Native Home Assistant subentry UI integration
- Automatic parent-child relationship management
- Independent configuration of each element
- Proper device registry association via
config_subentry_id - Easy addition/removal through native UI
- No manual
parent_entry_idtracking required
Architecture¶
graph TD
A[Add Integration] --> B{Entry Type?}
B -->|First Time| C[Hub Flow]
B -->|Hub Exists| D[Element Type Selection]
C --> E[Optimization Settings]
E --> F[Create Hub Entry]
D --> G{Element Type}
G -->|Battery| H[Battery Flow]
G -->|Grid| I[Grid Flow]
G -->|PV| J[PV Flow]
G -->|Load| K[Load Flow]
H --> L[Select Parent Hub]
I --> L
J --> L
K --> L
L --> M[Configure Element]
M --> N[Create Subentry]
Hub Flow¶
The hub flow creates the main integration entry that acts as a parent for element subentries and hosts the optimization coordinator.
Hub entry structure¶
Hub entries are identified by the presence of integration_type: "hub" in their data:
{
"entry_id": "abc123...",
"domain": "haeo",
"title": "Home Energy System",
"data": {
"integration_type": "hub" # Marker to identify hub entries
},
"options": {
"horizon_hours": 48,
"period_minutes": 5,
},
}
Optimization settings are stored in options (user-editable), while the hub marker is stored in data (immutable).
The hub flow implementation is in custom_components/haeo/flows/hub.py.
Key implementation points¶
- Hub flow uses standard config flow pattern with user step
- Prevents duplicate hub names by checking existing entries
- Stores optimization settings in
optionsfor later editing via options flow - Hub marker in
dataallows coordinator to identify hub entries
Element Flows¶
Element subentries are created through separate config flows, one per element type. All element flows inherit from a common base class that handles parent hub selection and entry creation.
Element entry structure¶
Element entries link to their parent hub via parent_entry_id:
{
"entry_id": "def456...",
"domain": "haeo",
"title": "Home Battery",
"data": {
"element_type": "battery",
"parent_entry_id": "abc123...", # Links to hub entry
"capacity": 13500,
"charge_power": 5000,
# ... element-specific configuration
},
}
Base element flow pattern¶
All element flows extend ElementConfigFlow which provides:
- Parent hub selection (auto-selects if only one hub exists)
- Entry creation with proper parent linkage
- Duplicate prevention
- Standard error handling
The element flow base class is in custom_components/haeo/flows/element.py.
Connection endpoint filtering¶
Connection elements require selecting source and target endpoints from other configured elements. The element flow filters available elements based on connectivity level and Advanced Mode setting.
Each element type in the ELEMENT_TYPES registry defines a connectivity level that controls when it appears in connection selectors.
The ConnectivityLevel enum has three values:
ALWAYS: Always shown in connection selectorsADVANCED: Only shown when Advanced Mode is enabledNEVER: Never shown in connection selectors
This filtering ensures connection endpoints are appropriate for the user's configuration level.
It prevents invalid connection topologies by excluding elements that shouldn't be connection endpoints.
See custom_components/haeo/elements/__init__.py for the connectivity level assigned to each element type.
Element-specific implementations¶
Each element type has its own flow class in custom_components/haeo/flows/:
BatteryConfigFlow- Battery element configurationGridConfigFlow- Grid configurationSolarConfigFlow- Solar system configurationConstantLoadConfigFlow- Constant load configurationForecastLoadConfigFlow- Forecast-based load configurationNodeConfigFlow- Network node configuration
Each flow defines element-specific schema fields, defaults, and validation logic.
ChooseSelector Config Flow Pattern¶
Element types use a single-step configuration flow with Home Assistant's ChooseSelector.
This selector provides a dropdown letting users pick between different input methods for each field.
Input Options¶
Each configurable field uses a ChooseSelector with these options:
| Choice | Description | Storage Format |
|---|---|---|
| Entity | Value comes from Home Assistant sensors | Entity ID string or list |
| Constant | User enters a fixed value directly | Numeric value or boolean |
| None | Field is not used (optional fields only) | Field omitted from config |
Required fields offer "Entity" and "Constant" choices. Optional fields also include "None" to skip the constraint entirely.
Single-Step Flow¶
All element configuration happens in a single step:
- User enters element name and connection target
- For each input field, user selects "Entity", "Constant", or "None"
- Based on selection, user either picks entities from a dropdown or enters a constant value inline
- Submit creates the element with all configuration
This replaces the previous two-step pattern where constant values were entered in a separate step.
Implementation Pattern¶
The ChooseSelector utilities are in custom_components/haeo/flows/field_schema.py:
build_choose_schema_entry(): Creates the ChooseSelector with entity/constant/none optionsbuild_choose_selector(): Builds the selector with proper choice orderingget_preferred_choice(): Determines which choice should be pre-selected based on defaults or current configget_choose_default(): Provides default values for the nested selectorconvert_choose_data_to_config(): Converts ChooseSelector output to final config format
The choice ordering in ChooseSelector determines the pre-selected option.
The get_preferred_choice() function examines field defaults and current configuration to order choices appropriately.
Entity Creation¶
HAEO creates input entities (number/switch) for fields configured with "Constant". These entities allow runtime adjustment of constant values without reconfiguring the element. Fields linked to external entities use those entities directly without creating HAEO input entities.
Field Schema System¶
HAEO uses a typed schema system to define element configuration fields. The schema system provides type safety, validation, and data loading from Home Assistant entities.
ELEMENT_TYPES Registry¶
All element types are registered in custom_components/haeo/elements/__init__.py:
ELEMENT_TYPES: dict[ElementType, ElementRegistryEntry] = {
"battery": ElementRegistryEntry(
schema=BatteryConfigSchema, # TypedDict for UI configuration
data=BatteryConfigData, # TypedDict for loaded values
defaults={...}, # Default field values
translation_key="battery", # For UI localization
adapter=create_battery, # Creates model elements
extract=extract_battery, # Extracts optimization results
),
# ... other element types
}
The registry provides:
- Schema/Data class pairs: Dual TypedDict pattern for type safety
- Default values: Pre-populated fields in the UI
- Adapter functions: Convert loaded data to model elements
- Result extractors: Convert optimization results to sensor data
Schema vs Data Mode¶
Each element type has two TypedDict definitions:
Schema mode (*ConfigSchema): What the user enters in the UI
- Contains entity IDs as strings
- Used during config flow form display and validation
Data mode (*ConfigData): What the optimizer uses
- Contains loaded numeric values
- Produced by the
load()function during coordinator updates
# Schema mode: entity IDs with Annotated metadata
class BatteryConfigSchema(TypedDict):
capacity: EnergySensorFieldSchema # Annotated[str, EntitySelect(...), TimeSeries(...)]
# Data mode: loaded values
class BatteryConfigData(TypedDict):
capacity: EnergySensorFieldData # Annotated[list[float], ...]
Field Metadata with Annotated¶
Fields use Annotated types with composable metadata markers:
from typing import Annotated
# Define field type with composed metadata
EnergySensorFieldSchema = Annotated[
str,
EntitySelect(accepted_units=ENERGY_UNITS),
TimeSeries(accepted_units=ENERGY_UNITS),
]
class BatteryConfigSchema(TypedDict):
capacity: EnergySensorFieldSchema # Entity ID with attached metadata
The composable metadata types are:
- Validator subclasses: Define schema validation and UI selectors (e.g.,
PositiveKW,Percentage,EntitySelect) - LoaderMeta subclasses: Specify how values are loaded at runtime (e.g.,
ConstantFloat,TimeSeries) - Default: Provides default values for config flow UI forms
Available Field Types¶
Field types are defined in custom_components/haeo/schema/fields.py:
| Validator Class | Purpose | Base Type |
|---|---|---|
PositiveKW |
Positive power values in kW | float |
AnyKW |
Power flow (pos or neg) in kW | float |
PositiveKWH |
Positive energy values in kWh | float |
Price |
Price values in $/kWh | float |
Percentage |
Percentage values (0-100) | float |
BatterySOC |
Battery SOC percentage | float |
Boolean |
Boolean flags | bool |
Name |
Free-form text names | str |
ElementName |
References to other elements | str |
EntitySelect |
Entity sensor references | str |
Data Loading Flow¶
The schema system integrates with data loading:
- User enters entity IDs in config flow (Schema mode)
- Voluptuous validators ensure valid entity selection
- On coordinator update,
load()converts Schema → Data mode - LoaderMeta markers determine which loader extracts values from Home Assistant
- Adapter functions receive Data mode config to create model elements
For details on loaders, see Data Loading.
Options Flow¶
The options flow allows users to edit hub optimization settings after initial setup. Elements are managed as separate config entries (added/edited/removed through the main integration flow), not through the options flow.
The options flow implementation is in custom_components/haeo/flows/hub.py.
Key points¶
- Options flow only edits hub-level optimization settings (horizon, period, solver)
- Element configuration happens via separate config entries
- Settings stored in
config_entry.options(notdata) - Changes trigger coordinator reload to apply new parameters
Element Management¶
Elements are not managed through the hub's options flow. Instead, users add/edit/remove elements as independent config entries through the main "Add Integration" flow.
User workflow¶
Adding elements:
- Navigate to Settings → Devices & Services
- Click Add Integration
- Search for "HAEO"
- Select element type (battery, grid, etc.)
- Choose parent hub
- Configure element parameters
Editing elements:
- Find the element entry in Devices & Services
- Click Configure on the element entry
- Modify parameters
- Submit changes
Removing elements:
- Find the element entry in Devices & Services
- Click the three-dot menu
- Select Delete
The hub coordinator automatically detects element changes on the next update cycle. See user configuration guide for end-user instructions.
Testing Config Flow¶
Config flow testing uses Home Assistant's testing fixtures and follows standard patterns.
Comprehensive test coverage is in tests/test_config_flow.py, including:
- Hub flow success and duplicate prevention
- Element flow with hub selection
- Options flow for editing settings
- Error handling scenarios
- Validation logic
Example test pattern:
async def test_hub_flow_success(hass: HomeAssistant) -> None:
"""Test successful hub creation."""
result = await hass.config_entries.flow.async_init(DOMAIN, context={"source": config_entries.SOURCE_USER})
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={CONF_NAME: "Test Hub", CONF_HORIZON_HOURS: 48},
)
assert result["type"] == FlowResultType.CREATE_ENTRY
assert result["data"] == {INTEGRATION_TYPE: INTEGRATION_TYPE_HUB}
Related Documentation¶
-
Architecture
Overall system design.
-
Data Loading
Field types and data validation.
-
Energy Models
Element type implementations.
-
Testing
Testing patterns for config flows.
-
User Configuration Guide
End-user configuration instructions.
-
Home Assistant Config Flow
Upstream pattern documentation.
-
Data Entry Flow
Flow framework reference.