Initil after Upgrade

This commit is contained in:
2026-06-15 10:53:52 -04:00
parent 2fe9bf0dd6
commit 887feaa50a
143 changed files with 2288 additions and 881 deletions
@@ -1,9 +1,8 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Any, cast
from typing import TYPE_CHECKING, Any
from homeassistant.config_entries import ConfigFlowResult
from homeassistant.data_entry_flow import FlowResult
from homeassistant.helpers import selector, translation
import voluptuous as vol
@@ -31,10 +30,23 @@ from custom_components.powercalc.discovery import (
)
from custom_components.powercalc.flow_helper.common import FlowType, PowercalcFormStep, Step
from custom_components.powercalc.flow_helper.dynamic_field_builder import build_dynamic_field_schema
from custom_components.powercalc.flow_helper.schema import SCHEMA_ENERGY_SENSOR_TOGGLE, SCHEMA_UTILITY_METER_TOGGLE, build_sub_profile_schema
from custom_components.powercalc.helpers import collect_placeholders, iter_related_entity_placeholders, resolve_related_entity_placeholder
from custom_components.powercalc.flow_helper.schema import (
SCHEMA_ENERGY_SENSOR_TOGGLE,
SCHEMA_UTILITY_METER_TOGGLE,
build_sub_profile_schema,
)
from custom_components.powercalc.helpers import (
collect_placeholders,
iter_related_entity_placeholders,
resolve_related_entity_placeholder,
)
from custom_components.powercalc.power_profile.library import ModelInfo, ProfileLibrary
from custom_components.powercalc.power_profile.power_profile import DEVICE_TYPE_DOMAIN, DOMAIN_DEVICE_TYPE_MAPPING, DiscoveryBy, PowerProfile
from custom_components.powercalc.power_profile.power_profile import (
DEVICE_TYPE_DOMAIN,
DOMAIN_DEVICE_TYPE_MAPPING,
DiscoveryBy,
PowerProfile,
)
if TYPE_CHECKING:
from custom_components.powercalc.config_flow import PowercalcCommonFlow, PowercalcConfigFlow, PowercalcOptionsFlow
@@ -67,7 +79,7 @@ class LibraryFlow:
async def async_step_manufacturer(
self,
user_input: dict[str, Any] | None = None,
) -> FlowResult:
) -> ConfigFlowResult:
"""Ask the user to select the manufacturer."""
async def _create_schema() -> vol.Schema:
@@ -82,7 +94,10 @@ class LibraryFlow:
]
return vol.Schema(
{
vol.Required(CONF_MANUFACTURER, default=self.flow.sensor_config.get(CONF_MANUFACTURER)): selector.SelectSelector(
vol.Required(
CONF_MANUFACTURER,
default=self.flow.sensor_config.get(CONF_MANUFACTURER),
): selector.SelectSelector(
selector.SelectSelectorConfig(
options=manufacturers,
mode=selector.SelectSelectorMode.DROPDOWN,
@@ -104,7 +119,7 @@ class LibraryFlow:
async def async_step_model(
self,
user_input: dict[str, Any] | None = None,
) -> FlowResult:
) -> ConfigFlowResult:
"""Ask the user to select the model."""
def _build_model_label(model_id: str, model_name: str) -> str:
@@ -139,10 +154,18 @@ class LibraryFlow:
self._get_library_discovery_by(),
)
]
model = self.flow.selected_profile.model if self.flow.selected_profile else self.flow.sensor_config.get(CONF_MODEL)
model = (
self.flow.selected_profile.model
if self.flow.selected_profile
else self.flow.sensor_config.get(CONF_MODEL)
)
return vol.Schema(
{
vol.Required(CONF_MODEL, description={"suggested_value": model}, default=model): selector.SelectSelector(
vol.Required(
CONF_MODEL,
description={"suggested_value": model},
default=model,
): selector.SelectSelector(
selector.SelectSelectorConfig(
options=models,
mode=selector.SelectSelectorMode.DROPDOWN,
@@ -162,26 +185,29 @@ class LibraryFlow:
user_input,
)
async def async_step_post_library(
self,
user_input: dict[str, Any] | None = None,
) -> FlowResult:
async def async_step_post_library(self, _: dict[str, Any] | None = None) -> ConfigFlowResult:
"""
Handles the logic after the user either selected manufacturer/model himself or confirmed autodiscovered.
Forwards to the next step in the flow.
"""
if not self.flow.selected_profile:
return self.flow.async_abort(reason="model_not_supported") # type:ignore # pragma: no cover
return self.flow.async_abort(reason="model_not_supported") # pragma: no cover
if Step.LIBRARY_CUSTOM_FIELDS not in self.flow.handled_steps and self.flow.selected_profile.has_custom_fields:
return await self.async_step_library_custom_fields()
if Step.AVAILABILITY_ENTITY not in self.flow.handled_steps and self.flow.selected_profile.discovery_by == DiscoveryBy.DEVICE:
if (
Step.AVAILABILITY_ENTITY not in self.flow.handled_steps
and self.flow.selected_profile.discovery_by == DiscoveryBy.DEVICE
):
result = await self.async_step_availability_entity()
if result:
return result
if Step.SUB_PROFILE not in self.flow.handled_steps and await self.flow.selected_profile.requires_manual_sub_profile_selection:
if (
Step.SUB_PROFILE not in self.flow.handled_steps
and await self.flow.selected_profile.requires_manual_sub_profile_selection
):
return await self.async_step_sub_profile()
if (
@@ -191,21 +217,26 @@ class LibraryFlow:
):
return await self.async_step_smart_switch()
if Step.FIXED not in self.flow.handled_steps and self.flow.selected_profile.needs_fixed_config: # pragma: no cover
return await self.flow.flow_handlers[FlowType.VIRTUAL_POWER].async_step_fixed() # type:ignore
if (
Step.FIXED not in self.flow.handled_steps and self.flow.selected_profile.needs_fixed_config
): # pragma: no cover
return await self.flow.flow_handlers[FlowType.VIRTUAL_POWER].async_step_fixed() # type: ignore[no-any-return]
if Step.LINEAR not in self.flow.handled_steps and self.flow.selected_profile.needs_linear_config:
return await self.flow.flow_handlers[FlowType.VIRTUAL_POWER].async_step_linear() # type:ignore
return await self.flow.flow_handlers[FlowType.VIRTUAL_POWER].async_step_linear() # type: ignore[no-any-return]
if Step.MULTI_SWITCH not in self.flow.handled_steps and self.flow.selected_profile.calculation_strategy == CalculationStrategy.MULTI_SWITCH:
return await self.flow.flow_handlers[FlowType.VIRTUAL_POWER].async_step_multi_switch() # type:ignore
if (
Step.MULTI_SWITCH not in self.flow.handled_steps
and self.flow.selected_profile.calculation_strategy == CalculationStrategy.MULTI_SWITCH
):
return await self.flow.flow_handlers[FlowType.VIRTUAL_POWER].async_step_multi_switch() # type: ignore[no-any-return]
return await self.flow.flow_handlers[FlowType.GROUP].async_step_assign_groups() # type:ignore
return await self.flow.flow_handlers[FlowType.GROUP].async_step_assign_groups() # type: ignore[no-any-return]
async def async_step_library_custom_fields(self, user_input: dict[str, Any] | None = None) -> FlowResult:
async def async_step_library_custom_fields(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult:
"""Handle the flow for custom fields."""
async def _process_user_input(user_input: dict[str, Any]) -> dict[str, Any]:
def _process_user_input(user_input: dict[str, Any]) -> dict[str, Any]:
return {CONF_VARIABLES: user_input}
form_kwarg: dict[str, Any] | None = None
@@ -234,11 +265,11 @@ class LibraryFlow:
async def async_step_sub_profile(
self,
user_input: dict[str, Any] | None = None,
) -> FlowResult:
) -> ConfigFlowResult:
"""Handle the flow for sub profile selection."""
assert self.flow.selected_profile is not None
async def _validate(user_input: dict[str, Any]) -> dict[str, str]:
def _validate(user_input: dict[str, Any]) -> dict[str, str]:
return {CONF_MODEL: f"{self.flow.sensor_config.get(CONF_MODEL)}/{user_input.get(CONF_SUB_PROFILE)}"}
library = await ProfileLibrary.factory(self.flow.hass)
@@ -254,7 +285,11 @@ class LibraryFlow:
if remarks:
remarks = "\n\n" + remarks
step = Step.SUB_PROFILE_PER_DEVICE if self.flow.selected_profile.discovery_by == DiscoveryBy.DEVICE else Step.SUB_PROFILE
step = (
Step.SUB_PROFILE_PER_DEVICE
if self.flow.selected_profile.discovery_by == DiscoveryBy.DEVICE
else Step.SUB_PROFILE
)
return await self.flow.handle_form_step(
PowercalcFormStep(
@@ -275,16 +310,16 @@ class LibraryFlow:
async def async_step_sub_profile_per_device(
self,
user_input: dict[str, Any] | None = None,
) -> FlowResult:
) -> ConfigFlowResult:
return await self.async_step_sub_profile(user_input)
async def async_step_smart_switch(self, user_input: dict[str, Any] | None = None) -> FlowResult:
async def async_step_smart_switch(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult:
"""Asks the user for the power of connect appliance for the smart switch."""
if self.flow.selected_profile and not self.flow.selected_profile.needs_fixed_config:
return self.flow.persist_config_entry()
async def _validate(user_input: dict[str, Any]) -> dict[str, Any]:
def _validate(user_input: dict[str, Any]) -> dict[str, Any]:
return {
CONF_SELF_USAGE_INCLUDED: user_input.get(CONF_SELF_USAGE_INCLUDED),
CONF_MODE: CalculationStrategy.FIXED,
@@ -303,7 +338,7 @@ class LibraryFlow:
user_input,
)
async def async_step_availability_entity(self, user_input: dict[str, Any] | None = None) -> FlowResult | None:
async def async_step_availability_entity(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult | None:
"""Handle the flow for availability entity."""
# Auto-resolve availability entity from profile placeholders
auto_entity = self._resolve_availability_entity()
@@ -377,7 +412,7 @@ class LibraryConfigFlow(LibraryFlow):
async def async_step_library_multi_profile(
self,
user_input: dict[str, Any] | None = None,
) -> FlowResult | ConfigFlowResult:
) -> ConfigFlowResult:
"""This step gets executed when multiple profiles are found for the source entity."""
if user_input is not None:
selected_model: str = user_input.get(CONF_MODEL) # type: ignore
@@ -426,7 +461,7 @@ class LibraryConfigFlow(LibraryFlow):
async def async_step_library(
self,
user_input: dict[str, Any] | None = None,
) -> FlowResult:
) -> ConfigFlowResult:
"""Try to autodiscover manufacturer/model first.
Ask the user to confirm this or forward to manual library selection.
"""
@@ -439,7 +474,7 @@ class LibraryConfigFlow(LibraryFlow):
return self._show_autodiscovered_profile_form()
async def _handle_library_confirmation(self, user_input: dict[str, Any]) -> FlowResult:
async def _handle_library_confirmation(self, user_input: dict[str, Any]) -> ConfigFlowResult:
"""Handle the user's response to an autodiscovered library profile."""
if not user_input.get(CONF_CONFIRM_AUTODISCOVERED_MODEL) or not self.flow.selected_profile:
return await self.async_step_manufacturer()
@@ -454,27 +489,31 @@ class LibraryConfigFlow(LibraryFlow):
async def _async_autodiscover_profile(self) -> None:
"""Populate the selected profile from the source entity when possible."""
if not self.flow.source_entity or not self.flow.source_entity.entity_entry or self.flow.selected_profile is not None:
if (
not self.flow.source_entity
or not self.flow.source_entity.entity_entry
or self.flow.selected_profile is not None
):
return
self.flow.selected_profile = await get_power_profile_by_source_entity(self.flow.hass, self.flow.source_entity)
if self.flow.selected_profile is None and self.flow.source_entity.device_entry:
self.flow.selected_profile = await get_power_profile_by_source_device(self.flow.hass, self.flow.source_entity)
self.flow.selected_profile = await get_power_profile_by_source_device(
self.flow.hass,
self.flow.source_entity,
)
def _show_autodiscovered_profile_form(self) -> FlowResult:
def _show_autodiscovered_profile_form(self) -> ConfigFlowResult:
"""Show the confirmation form for an autodiscovered library profile."""
profile = self.flow.selected_profile
assert profile is not None
return cast(
FlowResult,
self.flow.async_show_form(
step_id=Step.LIBRARY,
description_placeholders=self._build_library_description_placeholders(profile),
data_schema=SCHEMA_POWER_AUTODISCOVERED,
errors={},
last_step=False,
),
return self.flow.async_show_form(
step_id=Step.LIBRARY,
description_placeholders=self._build_library_description_placeholders(profile),
data_schema=SCHEMA_POWER_AUTODISCOVERED,
errors={},
last_step=False,
)
def _build_library_description_placeholders(self, profile: PowerProfile) -> dict[str, Any]:
@@ -499,9 +538,19 @@ class LibraryConfigFlow(LibraryFlow):
def _get_profile_source(self, profile: PowerProfile) -> str:
"""Build the autodiscovery source description."""
translations = translation.async_get_cached_translations(self.flow.hass, self.flow.hass.config.language, "common", DOMAIN)
if profile.discovery_by == DiscoveryBy.DEVICE and self.flow.source_entity and self.flow.source_entity.device_entry:
return f"{translations.get(f'component.{DOMAIN}.common.source_device')}: {self.flow.source_entity.device_entry.name}"
translations = translation.async_get_cached_translations(
self.flow.hass,
self.flow.hass.config.language,
"common",
DOMAIN,
)
if (
profile.discovery_by == DiscoveryBy.DEVICE
and self.flow.source_entity
and self.flow.source_entity.device_entry
):
label = translations.get(f"component.{DOMAIN}.common.source_device")
return f"{label}: {self.flow.source_entity.device_entry.name}"
return f"{translations.get(f'component.{DOMAIN}.common.source_entity')}: {self.flow.source_entity_id}"
@@ -537,7 +586,7 @@ class LibraryOptionsFlow(LibraryFlow):
super().__init__(flow)
self.flow: PowercalcOptionsFlow = flow
async def async_step_library_options(self, user_input: dict[str, Any] | None = None) -> FlowResult:
async def async_step_library_options(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult:
"""Handle the basic options flow."""
self.flow.is_library_flow = True
self.flow.selected_sub_profile = self.flow.selected_profile.sub_profile # type: ignore