Files
HomeAssistantVS/custom_components/powercalc/config_flow.py
T

744 lines
29 KiB
Python

"""Config flow for Powercalc integration."""
from abc import ABC, abstractmethod
from collections.abc import Awaitable, Callable
from inspect import isawaitable
import logging
from typing import Any
import uuid
from homeassistant.config_entries import (
ConfigEntry,
ConfigEntryBaseFlow,
ConfigFlow,
ConfigFlowResult,
OptionsFlow,
)
from homeassistant.const import (
CONF_DEVICE,
CONF_ENTITY_ID,
CONF_NAME,
CONF_UNIQUE_ID,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.data_entry_flow import section
from homeassistant.helpers import entity_registry as er, selector
from homeassistant.helpers.schema_config_entry_flow import SchemaFlowError
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
import voluptuous as vol
from .common import SourceEntity, create_source_entity
from .const import (
CONF_CREATE_COST_SENSOR,
CONF_CREATE_UTILITY_METERS,
CONF_ENERGY_PRICE_SENSOR,
CONF_MANUFACTURER,
CONF_MODE,
CONF_MODEL,
CONF_POWER,
CONF_SENSOR_TYPE,
CONF_STANDBY_POWER,
DISCOVERY_POWER_PROFILES,
DISCOVERY_SOURCE_ENTITY,
DOMAIN,
DUMMY_ENTITY_ID,
ENTRY_GLOBAL_CONFIG_UNIQUE_ID,
CalculationStrategy,
SensorType,
)
from .device_binding import attach_configured_device_entry
from .errors import ModelNotSupportedError, StrategyConfigurationError
from .flow_helper.common import FlowType, PowercalcFormStep, Step, fill_schema_defaults, flatten_sections
from .flow_helper.flows.cost import CostConfigFlow, CostOptionsFlow
from .flow_helper.flows.daily_energy import (
SCHEMA_DAILY_ENERGY_OPTIONS,
DailyEnergyConfigFlow,
DailyEnergyOptionsFlow,
build_daily_energy_config,
)
from .flow_helper.flows.global_configuration import (
GlobalConfigurationConfigFlow,
GlobalConfigurationOptionsFlow,
get_global_powercalc_config,
)
from .flow_helper.flows.group import (
GroupConfigFlow,
GroupOptionsFlow,
)
from .flow_helper.flows.library import SCHEMA_POWER_SMART_SWITCH, LibraryConfigFlow, LibraryOptionsFlow
from .flow_helper.flows.real_power import RealPowerConfigFlow, RealPowerOptionsFlow
from .flow_helper.flows.virtual_power import (
SCHEMA_POWER_ADVANCED,
STRATEGY_STEP_MAPPING,
VirtualPowerConfigFlow,
VirtualPowerOptionsFlow,
)
from .flow_helper.profile_preview import async_setup_preview as async_setup_powercalc_preview
from .flow_helper.schema import (
COST_DOCS_URI,
SCHEMA_COST_SENSOR_TOGGLE,
SCHEMA_ENERGY_SENSOR_TOGGLE,
SCHEMA_SENSOR_ENERGY_OPTIONS,
SCHEMA_STANDBY_ENERGY_SENSOR_TOGGLE,
SCHEMA_UTILITY_METER_OPTIONS,
SCHEMA_UTILITY_METER_TOGGLE,
build_cost_pricing_schema,
)
from .power_profile.factory import get_power_profile
from .power_profile.power_profile import SUPPORTED_DOMAINS, DeviceType, DiscoveryBy, PowerProfile
from .strategy.factory import PowerCalculatorStrategyFactory
_LOGGER = logging.getLogger(__name__)
MENU_SENSOR_TYPE = [
Step.VIRTUAL_POWER,
Step.MENU_LIBRARY,
Step.MENU_GROUP,
Step.DAILY_ENERGY,
Step.REAL_POWER,
Step.COST,
]
MENU_OPTIONS = [
Step.FIXED,
Step.LINEAR,
Step.MULTI_SWITCH,
Step.PLAYBOOK,
Step.WLED,
]
# Order matters: async_step() delegates to the first handler that defines the requested step.
FLOW_HANDLERS: dict[FlowType, dict[str, type[Any]]] = {
FlowType.GLOBAL_CONFIGURATION: {
"config": GlobalConfigurationConfigFlow,
"options": GlobalConfigurationOptionsFlow,
},
FlowType.LIBRARY: {
"config": LibraryConfigFlow,
"options": LibraryOptionsFlow,
},
FlowType.VIRTUAL_POWER: {
"config": VirtualPowerConfigFlow,
"options": VirtualPowerOptionsFlow,
},
FlowType.GROUP: {
"config": GroupConfigFlow,
"options": GroupOptionsFlow,
},
FlowType.DAILY_ENERGY: {
"config": DailyEnergyConfigFlow,
"options": DailyEnergyOptionsFlow,
},
FlowType.REAL_POWER: {
"config": RealPowerConfigFlow,
"options": RealPowerOptionsFlow,
},
FlowType.COST: {
"config": CostConfigFlow,
"options": CostOptionsFlow,
},
}
# noinspection PyTypeChecker
class PowercalcCommonFlow(ABC, ConfigEntryBaseFlow):
def __init__(self) -> None:
"""Initialize options flow."""
self.sensor_config: ConfigType = {}
self.global_config: ConfigType = {}
self.source_entity: SourceEntity | None = None
self.source_entity_id: str | None = None
self.selected_profile: PowerProfile | None = None
self.selected_sub_profile: str | None = None
self.is_library_flow: bool = False
self.skip_advanced_step: bool = False
self.selected_sensor_type: SensorType | None = None
self.is_options_flow: bool = isinstance(self, OptionsFlow)
self.strategy: CalculationStrategy | None = None
self.name: str | None = None
self.handled_steps: list[Step] = []
# Initialize flow handlers. Iteration order follows FLOW_HANDLERS, which async_step() relies on.
flow_key = "options" if self.is_options_flow else "config"
self.flow_handlers = {flow_type: handlers[flow_key](self) for flow_type, handlers in FLOW_HANDLERS.items()}
for step in Step:
step_method = f"async_step_{step}"
if hasattr(self, step_method):
continue
setattr(self, step_method, self._async_step(step))
super().__init__()
@staticmethod
async def async_setup_preview(hass: HomeAssistant) -> None:
"""Set up the config flow preview websocket command."""
await async_setup_powercalc_preview(hass)
@abstractmethod
@callback
def persist_config_entry(self) -> ConfigFlowResult:
pass # pragma: no cover
def _async_step(self, step: Step) -> Callable[[dict[str, Any] | None], Awaitable[ConfigFlowResult]]:
"""Generate a step handler."""
async def _async_step(
user_input: dict[str, Any] | None = None,
) -> ConfigFlowResult:
"""Handle a config flow step."""
return await self.async_step(step, user_input)
return _async_step
async def async_step(self, step: Step, user_input: dict[str, Any] | None = None) -> ConfigFlowResult:
"""Handle a config flow step by delegating to specific handler."""
step_method = f"async_step_{step}"
for handler in self.flow_handlers.values():
if hasattr(handler, step_method):
return await getattr(handler, step_method)(user_input) # type: ignore[no-any-return]
raise SchemaFlowError("No handler defined") # pragma: nocover
async def validate_strategy_config(self, user_input: dict[str, Any] | None = None) -> None:
"""Validate the strategy config."""
strategy_name = CalculationStrategy(
self.sensor_config.get(CONF_MODE) or self.selected_profile.calculation_strategy, # type: ignore
)
factory = PowerCalculatorStrategyFactory(self.hass)
assert self.source_entity is not None
try:
await factory.create(
user_input or self.sensor_config,
strategy_name,
self.selected_profile,
self.source_entity,
)
except StrategyConfigurationError as error:
_LOGGER.error(str(error))
raise SchemaFlowError(error.get_config_flow_translate_key() or "unknown") from error
def create_source_entity_selector(
self,
) -> selector.EntitySelector:
"""Create the entity selector for the source entity."""
if self.is_library_flow:
return selector.EntitySelector(
selector.EntitySelectorConfig(domain=list(SUPPORTED_DOMAINS)),
)
return selector.EntitySelector()
def create_device_entity_selector(self, domains: list[str], multiple: bool = False) -> selector.EntitySelector:
entity_registry = er.async_get(self.hass)
if self.source_entity and self.source_entity.device_entry:
entities = [
entity.entity_id
for entity in entity_registry.entities.get_entries_for_device_id(self.source_entity.device_entry.id)
if entity.domain in domains
]
else:
entities = []
return selector.EntitySelector(
selector.EntitySelectorConfig(
domain=domains,
multiple=multiple,
include_entities=entities,
),
)
async def handle_form_step(
self,
form_step: PowercalcFormStep,
user_input: dict[str, Any] | None = None,
) -> ConfigFlowResult:
"""Handle the current step."""
if user_input is None:
return await self._show_form(form_step)
try:
user_input = await self._validate_form_step_input(form_step, user_input)
except SchemaFlowError as exc:
return await self._show_form(form_step, exc)
self._store_form_step_input(form_step, user_input)
next_step = await self._resolve_next_step(form_step, user_input)
if next_step is None:
return await self.handle_final_steps(
skip_advanced=not form_step.continue_advanced_step,
skip_utility_meter_options=not form_step.continue_utility_meter_options_step,
)
return await getattr(self, f"async_step_{next_step}")() # type: ignore[no-any-return]
@staticmethod
async def _validate_form_step_input(
form_step: PowercalcFormStep,
user_input: dict[str, Any],
) -> dict[str, Any]:
if form_step.validate_user_input is None:
return user_input
validated_input = form_step.validate_user_input(user_input)
return await validated_input if isawaitable(validated_input) else validated_input # ty: ignore[invalid-return-type]
def _store_form_step_input(
self,
form_step: PowercalcFormStep,
user_input: dict[str, Any],
) -> None:
if CONF_NAME in user_input:
self.name = user_input[CONF_NAME]
self.sensor_config.update(user_input)
self.handled_steps.append(form_step.step)
@staticmethod
async def _resolve_next_step(
form_step: PowercalcFormStep,
user_input: dict[str, Any],
) -> Step | None:
if not callable(form_step.next_step):
return form_step.next_step
next_step = form_step.next_step(user_input)
return await next_step if isawaitable(next_step) else next_step
async def handle_final_steps(
self,
skip_advanced: bool = False,
skip_utility_meter_options: bool = False,
) -> ConfigFlowResult:
"""Handle the final steps of the flow if needed and persist the config entry."""
if not skip_advanced and self.selected_sensor_type == SensorType.VIRTUAL_POWER:
return await self.flow_handlers[FlowType.VIRTUAL_POWER].async_step_power_advanced() # type: ignore[no-any-return]
if not skip_utility_meter_options and self.sensor_config.get(CONF_CREATE_UTILITY_METERS):
return await self.async_step_utility_meter_options()
return self.persist_config_entry()
async def _show_form(self, form_step: PowercalcFormStep, error: SchemaFlowError | None = None) -> ConfigFlowResult:
# Show form for next step
last_step = None
if not callable(form_step.next_step):
last_step = form_step.next_step is None
schema = await self._get_schema(form_step)
# noinspection PyTypeChecker
form_data = form_step.form_data
if form_data is None:
form_data = {**self.sensor_config, **get_global_powercalc_config(self)}
return self.async_show_form(
step_id=form_step.step,
data_schema=fill_schema_defaults(schema, form_data),
errors={"base": str(error)} if error else {},
last_step=last_step,
**(form_step.form_kwarg or {}),
)
async def _get_schema(self, form_step: PowercalcFormStep) -> vol.Schema:
if isinstance(form_step.schema, vol.Schema):
return form_step.schema
schema = await form_step.schema()
if schema is None: # pragma: no cover
return vol.Schema({})
return schema
async def async_step_utility_meter_options(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult:
"""Handle the flow for utility meter options."""
return await self.handle_form_step(
PowercalcFormStep(
step=Step.UTILITY_METER_OPTIONS,
schema=SCHEMA_UTILITY_METER_OPTIONS,
),
user_input,
)
async def async_step_energy_options(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult:
"""Handle the flow for utility meter options."""
return await self.handle_form_step(
PowercalcFormStep(
step=Step.ENERGY_OPTIONS,
schema=SCHEMA_SENSOR_ENERGY_OPTIONS,
continue_utility_meter_options_step=not self.is_options_flow,
),
user_input,
)
class PowercalcConfigFlow(PowercalcCommonFlow, ConfigFlow, domain=DOMAIN):
"""Handle a config flow for PowerCalc."""
VERSION = 9
def __init__(self) -> None:
"""Initialize options flow."""
self.discovered_profiles: dict[str, PowerProfile] = {}
super().__init__()
@staticmethod
@callback
def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
"""Get the options flow for this handler."""
return PowercalcOptionsFlow(config_entry)
@callback
def abort_if_unique_id_configured(self) -> None:
self._abort_if_unique_id_configured()
async def async_step_integration_discovery(
self,
discovery_info: DiscoveryInfoType,
) -> ConfigFlowResult:
"""Handle integration discovery."""
_LOGGER.debug("Starting discovery flow: %s", discovery_info)
await self.async_set_unique_id(discovery_info.get(CONF_UNIQUE_ID, str(uuid.uuid4())))
self.selected_sensor_type = SensorType.VIRTUAL_POWER
self.source_entity = discovery_info[DISCOVERY_SOURCE_ENTITY]
del discovery_info[DISCOVERY_SOURCE_ENTITY]
if not self.source_entity:
return self.async_abort(reason="No source entity set") # pragma: no cover
self.source_entity_id = self.source_entity.entity_id
self.name = self.source_entity.name
power_profiles: list[PowerProfile] = []
if DISCOVERY_POWER_PROFILES in discovery_info:
power_profiles = discovery_info[DISCOVERY_POWER_PROFILES]
self.discovered_profiles = {profile.unique_id: profile for profile in power_profiles}
del discovery_info[DISCOVERY_POWER_PROFILES]
self.sensor_config = discovery_info.copy()
self.context["title_placeholders"] = {
"name": self.name or "",
"manufacturer": str(self.sensor_config.get(CONF_MANUFACTURER)),
"model": str(self.sensor_config.get(CONF_MODEL)),
}
self.skip_advanced_step = True # We don't want to ask advanced options when discovered
self.is_library_flow = True
if discovery_info.get(CONF_MODE) == CalculationStrategy.WLED:
return await self.flow_handlers[FlowType.VIRTUAL_POWER].async_step_wled() # type: ignore[no-any-return]
if len(power_profiles) > 1:
return await self.flow_handlers[FlowType.LIBRARY].async_step_library_multi_profile() # type: ignore[no-any-return]
self.selected_profile = power_profiles[0]
return await self.flow_handlers[FlowType.LIBRARY].async_step_library() # type: ignore[no-any-return]
async def async_step_user(
self,
user_input: dict[str, Any] | None = None,
) -> ConfigFlowResult:
"""Start flow when initialized by the user."""
self.is_library_flow = False
global_config_entry = self.hass.config_entries.async_entry_for_domain_unique_id(
DOMAIN,
ENTRY_GLOBAL_CONFIG_UNIQUE_ID,
)
menu = MENU_SENSOR_TYPE.copy()
if not global_config_entry:
menu.insert(0, Step.GLOBAL_CONFIGURATION)
await self.async_set_unique_id(str(uuid.uuid4()))
return self.async_show_menu(step_id=Step.USER, menu_options=menu)
async def async_step_menu_library(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult:
"""Handle the Virtual power (library) step.
We forward to the virtual_power step, but without the strategy selector displayed.
"""
self.is_library_flow = True
return await self.flow_handlers[FlowType.VIRTUAL_POWER].async_step_virtual_power(user_input) # type: ignore[no-any-return]
@callback
def persist_config_entry(self) -> ConfigFlowResult:
"""Create the config entry."""
entry_data: ConfigType = {
**self.sensor_config,
CONF_SENSOR_TYPE: self.selected_sensor_type,
CONF_NAME: self.name,
}
if self.source_entity_id:
entry_data[CONF_ENTITY_ID] = self.source_entity_id
profile = self.selected_profile
source_entity = self.source_entity
if profile and source_entity and profile.discovery_by == DiscoveryBy.DEVICE and source_entity.device_entry:
entry_data[CONF_DEVICE] = source_entity.device_entry.id
return self.async_create_entry(title=str(self.name), data=entry_data)
class PowercalcOptionsFlow(PowercalcCommonFlow, OptionsFlow):
"""Handle an option flow for PowerCalc."""
def __init__(self, config_entry: ConfigEntry) -> None:
"""Initialize options flow."""
super().__init__()
self.sensor_config = dict(config_entry.data)
self.strategy = self.sensor_config.get(CONF_MODE)
@callback
def abort_if_unique_id_configured(self) -> None:
"""Return if unique_id is already configured."""
# pragma: nocover
async def async_set_unique_id(self, unique_id: str | None, raise_on_progress: bool = True) -> None:
"""Set the unique ID of the flow."""
# pragma: nocover
async def async_step_init(
self,
user_input: dict[str, Any] | None = None,
) -> ConfigFlowResult:
"""Handle options flow."""
self.selected_sensor_type = self.sensor_config.get(CONF_SENSOR_TYPE) or SensorType.VIRTUAL_POWER
self.source_entity_id = self.sensor_config.get(CONF_ENTITY_ID)
if self.config_entry.unique_id == ENTRY_GLOBAL_CONFIG_UNIQUE_ID:
self.global_config = get_global_powercalc_config(self)
return self.async_show_menu(
step_id=Step.INIT,
menu_options=self.flow_handlers[FlowType.GLOBAL_CONFIGURATION].build_global_config_menu(),
)
self.sensor_config = dict(self.config_entry.data)
if self.source_entity_id:
self.source_entity = attach_configured_device_entry(
self.hass,
self.sensor_config,
create_source_entity(
self.source_entity_id,
self.hass,
),
)
result = await self.initialize_library_profile()
if result:
return result
return self.async_show_menu(step_id=Step.INIT, menu_options=self.build_menu())
async def initialize_library_profile(self) -> ConfigFlowResult | None:
"""Initialize the library profile, when manufacturer and model are set."""
manufacturer: str | None = self.sensor_config.get(CONF_MANUFACTURER)
model: str | None = self.sensor_config.get(CONF_MODEL)
if not manufacturer or not model:
return None
try:
self.selected_profile = await get_power_profile(
self.hass,
self.sensor_config,
self.source_entity,
process_variables=False,
)
if self.selected_profile and not self.strategy:
self.strategy = self.selected_profile.calculation_strategy
except ModelNotSupportedError:
return self.async_abort(reason="model_not_supported")
return None
def build_menu(self) -> list[Step]:
"""Build the options menu."""
if self.selected_sensor_type == SensorType.COST:
return [Step.COST]
menu = [Step.BASIC_OPTIONS]
if self.selected_sensor_type == SensorType.VIRTUAL_POWER:
if self.strategy and self.should_add_strategy_option_to_menu():
strategy_step = STRATEGY_STEP_MAPPING[self.strategy]
menu.append(strategy_step)
if self.selected_profile:
menu.append(Step.LIBRARY_OPTIONS)
menu.append(Step.ADVANCED_OPTIONS)
if self.selected_sensor_type == SensorType.DAILY_ENERGY:
menu.append(Step.DAILY_ENERGY)
if self.selected_sensor_type == SensorType.REAL_POWER:
menu.extend([Step.REAL_POWER, Step.ENERGY_OPTIONS])
if self.selected_sensor_type == SensorType.GROUP:
menu.extend(self.flow_handlers[FlowType.GROUP].build_group_menu())
if self.sensor_config.get(CONF_CREATE_COST_SENSOR):
menu.append(Step.COST_OPTIONS)
if self.sensor_config.get(CONF_CREATE_UTILITY_METERS):
menu.append(Step.UTILITY_METER_OPTIONS)
return menu
def should_add_strategy_option_to_menu(self) -> bool:
"""Check whether the strategy option should be added to the menu."""
if not self.strategy or self.strategy not in STRATEGY_STEP_MAPPING:
return False
if self.selected_profile:
if self.strategy == CalculationStrategy.FIXED and not self.selected_profile.needs_fixed_config:
return False
if self.strategy == CalculationStrategy.LINEAR and not self.selected_profile.needs_linear_config:
return False
return True
async def async_step_basic_options(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult:
"""Handle the basic options flow."""
return await self.async_handle_options_step(user_input, self.build_basic_options_schema(), Step.BASIC_OPTIONS)
async def async_step_advanced_options(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult:
"""Handle the basic options flow."""
return await self.async_handle_options_step(user_input, SCHEMA_POWER_ADVANCED, Step.ADVANCED_OPTIONS)
async def async_step_utility_meter_options(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult:
"""Handle the basic options flow."""
return await self.async_handle_options_step(
user_input,
SCHEMA_UTILITY_METER_OPTIONS,
Step.UTILITY_METER_OPTIONS,
)
async def async_step_cost_options(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult:
"""Handle the per sensor energy price override."""
return await self.async_handle_options_step(
user_input,
build_cost_pricing_schema(self.hass, self.sensor_config.get(CONF_ENERGY_PRICE_SENSOR)),
Step.COST_OPTIONS,
form_kwarg={"description_placeholders": {"docs_uri": COST_DOCS_URI}},
)
async def async_handle_options_step(
self,
user_input: dict[str, Any] | None,
schema: vol.Schema,
step: Step,
form_kwarg: dict[str, Any] | None = None,
validate: Callable[[dict[str, Any]], dict[str, str] | None] | None = None,
) -> ConfigFlowResult:
"""
Generic handler for all the option steps.
processes user input against the select schema.
And finally persist the changes on the config entry
An optional `validate` callback receives the user input with any collapsible sections
flattened, and returns errors to show on the form.
"""
errors: dict[str, str] | None = {}
schema = fill_schema_defaults(schema, self.sensor_config)
if user_input is not None:
errors = validate(flatten_sections(user_input, schema)) if validate else None
if not errors:
errors = await self.process_all_options(user_input, schema)
if not errors:
return self.persist_config_entry()
return self.async_show_form(step_id=step, data_schema=schema, errors=errors, **(form_kwarg or {}))
def persist_config_entry(self) -> ConfigFlowResult:
"""Persist changed options on the config entry."""
data = (
self.config_entry.unique_id == ENTRY_GLOBAL_CONFIG_UNIQUE_ID and self.global_config
) or self.sensor_config
self.hass.config_entries.async_update_entry(
self.config_entry,
data=data,
)
return self.async_create_entry(title="", data={})
async def process_all_options(self, user_input: dict[str, Any], schema: vol.Schema) -> dict[str, str] | None:
"""
Process the provided user input against the schema,
and save the options data in current_config to save later on
"""
assert self.cur_step is not None
current_step: Step = Step(str(self.cur_step["step_id"]))
is_strategy_step = current_step in STRATEGY_STEP_MAPPING.values()
if self.strategy and is_strategy_step:
if self.selected_profile and self.selected_profile.device_type == DeviceType.SMART_SWITCH:
self._process_user_input(user_input, SCHEMA_POWER_SMART_SWITCH)
user_input = {CONF_POWER: user_input.get(CONF_POWER, 0)}
strategy_options = await self.flow_handlers[FlowType.VIRTUAL_POWER].build_strategy_config(user_input or {})
if self.strategy != CalculationStrategy.LUT:
self.sensor_config.update({str(self.strategy): strategy_options})
try:
await self.validate_strategy_config()
return None
except SchemaFlowError as exc:
return {"base": str(exc)}
self._process_user_input(user_input, schema)
if self.selected_sensor_type == SensorType.DAILY_ENERGY and current_step == Step.DAILY_ENERGY:
self.sensor_config.update(build_daily_energy_config(user_input, SCHEMA_DAILY_ENERGY_OPTIONS))
if CONF_ENTITY_ID in user_input:
self.sensor_config[CONF_ENTITY_ID] = user_input[CONF_ENTITY_ID]
return None
def _process_user_input(
self,
user_input: dict[str, Any],
schema: vol.Schema,
) -> None:
"""
Process the provided user input against the schema.
Update current_config with the new options. Used to save the data to the config entry later.
"""
for key, val in schema.schema.items():
base_key = key.schema if isinstance(key, vol.Marker) else key
if isinstance(val, section):
# Recurse into collapsible sections, whose values are nested under the section key.
self._process_user_input(user_input.get(base_key) or {}, val.schema)
continue
if base_key in user_input:
self.sensor_config[base_key] = user_input.get(base_key)
elif base_key in self.sensor_config:
self.sensor_config.pop(base_key)
def build_basic_options_schema(self) -> vol.Schema:
"""Build the basic options schema. depending on the selected sensor type."""
if self.selected_sensor_type in [SensorType.REAL_POWER, SensorType.DAILY_ENERGY]:
return vol.Schema(
{
**SCHEMA_COST_SENSOR_TOGGLE.schema,
**SCHEMA_UTILITY_METER_TOGGLE.schema,
},
)
if self.selected_sensor_type == SensorType.GROUP:
return vol.Schema(
{
**SCHEMA_ENERGY_SENSOR_TOGGLE.schema,
**SCHEMA_COST_SENSOR_TOGGLE.schema,
**SCHEMA_UTILITY_METER_TOGGLE.schema,
},
)
schema = vol.Schema({})
if self.source_entity_id != DUMMY_ENTITY_ID:
schema = schema.extend(
{vol.Optional(CONF_ENTITY_ID): self.create_source_entity_selector()},
)
if not (self.selected_profile and self.selected_profile.only_self_usage):
schema = schema.extend(
{vol.Optional(CONF_STANDBY_POWER): vol.Coerce(float)},
)
return schema.extend( # type: ignore[no-any-return]
{
**SCHEMA_ENERGY_SENSOR_TOGGLE.schema,
**SCHEMA_STANDBY_ENERGY_SENSOR_TOGGLE.schema,
**SCHEMA_COST_SENSOR_TOGGLE.schema,
**SCHEMA_UTILITY_METER_TOGGLE.schema,
},
)