updated apps
This commit is contained in:
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
@@ -0,0 +1,75 @@
|
||||
"""Config/options flow for a standalone cost sensor based on an existing energy sensor."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from homeassistant.components.sensor import SensorDeviceClass
|
||||
from homeassistant.config_entries import ConfigFlowResult
|
||||
from homeassistant.const import CONF_NAME
|
||||
from homeassistant.helpers import selector
|
||||
import voluptuous as vol
|
||||
|
||||
from custom_components.powercalc.const import (
|
||||
CONF_ENERGY_PRICE,
|
||||
CONF_ENERGY_PRICE_SENSOR,
|
||||
CONF_ENERGY_SENSOR_ID,
|
||||
DOMAIN,
|
||||
DOMAIN_CONFIG,
|
||||
SensorType,
|
||||
)
|
||||
from custom_components.powercalc.flow_helper.common import PowercalcFormStep, Step
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from custom_components.powercalc.config_flow import PowercalcConfigFlow, PowercalcOptionsFlow
|
||||
|
||||
SCHEMA_COST_OPTIONS = vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_ENERGY_SENSOR_ID): selector.EntitySelector(
|
||||
selector.EntitySelectorConfig(domain="sensor", device_class=SensorDeviceClass.ENERGY),
|
||||
),
|
||||
},
|
||||
)
|
||||
|
||||
SCHEMA_COST = vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_NAME): selector.TextSelector(),
|
||||
**SCHEMA_COST_OPTIONS.schema,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def is_global_price_configured(hass: HomeAssistant) -> bool:
|
||||
"""Check whether a global energy price (fixed or sensor) has been configured."""
|
||||
global_config = hass.data.get(DOMAIN, {}).get(DOMAIN_CONFIG, {})
|
||||
return bool(global_config.get(CONF_ENERGY_PRICE) or global_config.get(CONF_ENERGY_PRICE_SENSOR))
|
||||
|
||||
|
||||
class CostConfigFlow:
|
||||
def __init__(self, flow: PowercalcConfigFlow) -> None:
|
||||
self.flow: PowercalcConfigFlow = flow
|
||||
|
||||
async def async_step_cost(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult:
|
||||
"""Handle the flow for a standalone cost sensor."""
|
||||
if not is_global_price_configured(self.flow.hass):
|
||||
return self.flow.async_abort(
|
||||
reason="cost_no_global_price",
|
||||
description_placeholders={"url": "https://docs.powercalc.nl/sensor-types/cost-sensor/"},
|
||||
)
|
||||
|
||||
self.flow.selected_sensor_type = SensorType.COST
|
||||
return await self.flow.handle_form_step(
|
||||
PowercalcFormStep(step=Step.COST, schema=SCHEMA_COST),
|
||||
user_input,
|
||||
)
|
||||
|
||||
|
||||
class CostOptionsFlow:
|
||||
def __init__(self, flow: PowercalcOptionsFlow) -> None:
|
||||
self.flow: PowercalcOptionsFlow = flow
|
||||
|
||||
async def async_step_cost(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult:
|
||||
"""Handle the cost sensor options flow."""
|
||||
return await self.flow.async_handle_options_step(user_input, SCHEMA_COST_OPTIONS, Step.COST)
|
||||
@@ -5,12 +5,17 @@ from typing import TYPE_CHECKING, Any
|
||||
|
||||
from homeassistant.config_entries import ConfigFlowResult
|
||||
from homeassistant.const import CONF_ENABLED, CONF_SENSORS, UnitOfTime
|
||||
from homeassistant.data_entry_flow import section
|
||||
from homeassistant.helpers import selector
|
||||
from homeassistant.helpers.schema_config_entry_flow import SchemaFlowError
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
import voluptuous as vol
|
||||
|
||||
from custom_components.powercalc import DeviceType
|
||||
from custom_components.powercalc.const import (
|
||||
CONF_APPLY_TO_ALL,
|
||||
CONF_CREATE_COST_SENSOR,
|
||||
CONF_CREATE_COST_SENSORS,
|
||||
CONF_CREATE_ENERGY_SENSORS,
|
||||
CONF_CREATE_STANDBY_GROUP,
|
||||
CONF_CREATE_UTILITY_METERS,
|
||||
@@ -18,6 +23,8 @@ from custom_components.powercalc.const import (
|
||||
CONF_DISABLE_LIBRARY_DOWNLOAD,
|
||||
CONF_DISCOVERY,
|
||||
CONF_ENABLE_ANALYTICS,
|
||||
CONF_ENERGY_PRICE,
|
||||
CONF_ENERGY_PRICE_SENSOR,
|
||||
CONF_ENERGY_SENSOR_CATEGORY,
|
||||
CONF_ENERGY_SENSOR_FRIENDLY_NAMING,
|
||||
CONF_ENERGY_SENSOR_NAMING,
|
||||
@@ -44,17 +51,27 @@ from custom_components.powercalc.const import (
|
||||
ENTITY_CATEGORIES,
|
||||
ENTRY_GLOBAL_CONFIG_UNIQUE_ID,
|
||||
)
|
||||
from custom_components.powercalc.flow_helper.common import PowercalcFormStep, Step
|
||||
from custom_components.powercalc.flow_helper.common import PowercalcFormStep, Step, flatten_sections
|
||||
from custom_components.powercalc.flow_helper.schema import (
|
||||
SCHEMA_COST_APPLY,
|
||||
SCHEMA_ENERGY_OPTIONS,
|
||||
SCHEMA_GLOBAL_COST,
|
||||
SCHEMA_GLOBAL_COST_FLAT,
|
||||
SCHEMA_UTILITY_METER_OPTIONS,
|
||||
SCHEMA_UTILITY_METER_TOGGLE,
|
||||
SECTION_COST_NAMING,
|
||||
SECTION_COST_PRICING,
|
||||
)
|
||||
from custom_components.powercalc.service.gui_configuration import apply_field_to_config_entries
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from custom_components.powercalc.config_flow import PowercalcCommonFlow, PowercalcConfigFlow, PowercalcOptionsFlow
|
||||
|
||||
SCHEMA_GLOBAL_CONFIGURATION = vol.Schema(
|
||||
SECTION_GLOBAL_POWER = "power_options"
|
||||
SECTION_GLOBAL_FEATURES = "features"
|
||||
SECTION_GLOBAL_ADVANCED = "advanced"
|
||||
|
||||
SCHEMA_GLOBAL_CONFIGURATION_POWER = vol.Schema(
|
||||
{
|
||||
vol.Optional(CONF_POWER_SENSOR_NAMING): selector.TextSelector(),
|
||||
vol.Optional(CONF_POWER_SENSOR_FRIENDLY_NAMING): selector.TextSelector(),
|
||||
@@ -67,20 +84,41 @@ SCHEMA_GLOBAL_CONFIGURATION = vol.Schema(
|
||||
vol.Optional(CONF_POWER_SENSOR_PRECISION): selector.NumberSelector(
|
||||
selector.NumberSelectorConfig(min=0, max=6, mode=selector.NumberSelectorMode.BOX, step=1),
|
||||
),
|
||||
},
|
||||
)
|
||||
|
||||
SCHEMA_GLOBAL_CONFIGURATION_FEATURES = vol.Schema(
|
||||
{
|
||||
vol.Optional(CONF_CREATE_ENERGY_SENSORS, default=True): selector.BooleanSelector(),
|
||||
vol.Optional(CONF_CREATE_COST_SENSORS, default=False): selector.BooleanSelector(),
|
||||
vol.Optional(CONF_CREATE_STANDBY_GROUP, default=True): selector.BooleanSelector(),
|
||||
**SCHEMA_UTILITY_METER_TOGGLE.schema,
|
||||
},
|
||||
)
|
||||
|
||||
SCHEMA_GLOBAL_CONFIGURATION_ADVANCED = vol.Schema(
|
||||
{
|
||||
vol.Optional(CONF_ENABLE_ANALYTICS, default=True): selector.BooleanSelector(),
|
||||
vol.Optional(CONF_IGNORE_UNAVAILABLE_STATE, default=False): selector.BooleanSelector(),
|
||||
vol.Optional(CONF_INCLUDE_NON_POWERCALC_SENSORS, default=True): selector.BooleanSelector(),
|
||||
vol.Optional(CONF_DISABLE_EXTENDED_ATTRIBUTES, default=False): selector.BooleanSelector(),
|
||||
vol.Optional(CONF_DISABLE_LIBRARY_DOWNLOAD, default=False): selector.BooleanSelector(),
|
||||
vol.Optional(CONF_CREATE_STANDBY_GROUP, default=True): selector.BooleanSelector(),
|
||||
vol.Optional(CONF_CREATE_ENERGY_SENSORS, default=True): selector.BooleanSelector(),
|
||||
**SCHEMA_UTILITY_METER_TOGGLE.schema,
|
||||
},
|
||||
)
|
||||
|
||||
# Presented in the GUI as three collapsible sections (power sensor, features, advanced).
|
||||
SCHEMA_GLOBAL_CONFIGURATION = vol.Schema(
|
||||
{
|
||||
vol.Required(SECTION_GLOBAL_POWER): section(SCHEMA_GLOBAL_CONFIGURATION_POWER),
|
||||
vol.Required(SECTION_GLOBAL_FEATURES): section(SCHEMA_GLOBAL_CONFIGURATION_FEATURES),
|
||||
vol.Required(SECTION_GLOBAL_ADVANCED): section(SCHEMA_GLOBAL_CONFIGURATION_ADVANCED, {"collapsed": True}),
|
||||
},
|
||||
)
|
||||
|
||||
SCHEMA_GLOBAL_CONFIGURATION_DISCOVERY = vol.Schema(
|
||||
{
|
||||
vol.Optional(CONF_ENABLED, default=True): selector.BooleanSelector(),
|
||||
vol.Optional(CONF_EXCLUDE_SELF_USAGE, default=False): selector.BooleanSelector(),
|
||||
vol.Optional(CONF_EXCLUDE_DEVICE_TYPES): selector.SelectSelector(
|
||||
selector.SelectSelectorConfig(
|
||||
options=[cls.value for cls in DeviceType],
|
||||
@@ -88,7 +126,6 @@ SCHEMA_GLOBAL_CONFIGURATION_DISCOVERY = vol.Schema(
|
||||
multiple=True,
|
||||
),
|
||||
),
|
||||
vol.Optional(CONF_EXCLUDE_SELF_USAGE, default=False): selector.BooleanSelector(),
|
||||
},
|
||||
)
|
||||
|
||||
@@ -139,13 +176,16 @@ def merge_global_config(global_config: ConfigType, user_input: dict[str, Any], s
|
||||
Keys present in the schema but absent from the user input were cleared in the form
|
||||
and must be removed, otherwise a previously saved value would incorrectly persist.
|
||||
"""
|
||||
for key in schema.schema:
|
||||
if isinstance(key, vol.Marker):
|
||||
key = key.schema
|
||||
if key in user_input:
|
||||
global_config[key] = user_input[key]
|
||||
elif key in global_config:
|
||||
global_config.pop(key)
|
||||
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.
|
||||
merge_global_config(global_config, user_input.get(base_key) or {}, val.schema)
|
||||
continue
|
||||
if base_key in user_input:
|
||||
global_config[base_key] = user_input[base_key]
|
||||
elif base_key in global_config:
|
||||
global_config.pop(base_key)
|
||||
|
||||
|
||||
def get_global_powercalc_config(flow: PowercalcCommonFlow) -> ConfigType:
|
||||
@@ -167,6 +207,11 @@ class GlobalConfigurationFlow:
|
||||
def __init__(self, flow: PowercalcCommonFlow) -> None:
|
||||
self.flow = flow
|
||||
|
||||
def is_energy_price_configured(self) -> bool:
|
||||
"""Check whether an energy price (fixed or sensor) has been configured globally."""
|
||||
config = self.flow.global_config
|
||||
return bool(config.get(CONF_ENERGY_PRICE) or config.get(CONF_ENERGY_PRICE_SENSOR))
|
||||
|
||||
async def async_step_global_configuration_discovery(
|
||||
self,
|
||||
user_input: dict[str, Any] | None = None,
|
||||
@@ -257,10 +302,7 @@ class GlobalConfigurationFlow:
|
||||
self.flow.global_config.update(user_input)
|
||||
|
||||
if not bool(self.flow.global_config.get(CONF_CREATE_UTILITY_METERS)) or user_input is not None:
|
||||
return self.flow.async_create_entry(
|
||||
title="Global Configuration",
|
||||
data=self.flow.global_config,
|
||||
)
|
||||
return await self.async_step_global_configuration_cost()
|
||||
|
||||
return await self.flow.handle_form_step(
|
||||
PowercalcFormStep(
|
||||
@@ -274,6 +316,65 @@ class GlobalConfigurationFlow:
|
||||
),
|
||||
)
|
||||
|
||||
async def async_step_global_configuration_cost(
|
||||
self,
|
||||
user_input: dict[str, Any] | None = None,
|
||||
) -> ConfigFlowResult:
|
||||
"""Handle the global cost sensor configuration step (energy price)."""
|
||||
|
||||
form_step = PowercalcFormStep(
|
||||
step=Step.GLOBAL_CONFIGURATION_COST,
|
||||
schema=SCHEMA_GLOBAL_COST,
|
||||
form_kwarg={
|
||||
"description_placeholders": {
|
||||
"docs_uri": "https://docs.powercalc.nl/sensor-types/cost-sensor/",
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
if user_input is not None:
|
||||
# The form presents pricing and naming as two sections, flatten them back to plain keys.
|
||||
user_input = {**user_input.get(SECTION_COST_PRICING, {}), **user_input.get(SECTION_COST_NAMING, {})}
|
||||
if not user_input.get(CONF_ENERGY_PRICE) and not user_input.get(CONF_ENERGY_PRICE_SENSOR):
|
||||
return await self.flow._show_form(form_step, SchemaFlowError("cost_price_mandatory")) # noqa: SLF001
|
||||
if self.flow.is_options_flow:
|
||||
merge_global_config(self.flow.global_config, user_input, SCHEMA_GLOBAL_COST_FLAT)
|
||||
return self.flow.persist_config_entry()
|
||||
self.flow.global_config.update(user_input)
|
||||
|
||||
if not bool(self.flow.global_config.get(CONF_CREATE_COST_SENSORS)) or user_input is not None:
|
||||
return self.flow.async_create_entry(
|
||||
title="Global Configuration",
|
||||
data=self.flow.global_config,
|
||||
)
|
||||
|
||||
return await self.flow.handle_form_step(form_step)
|
||||
|
||||
async def async_step_global_configuration_cost_apply(
|
||||
self,
|
||||
user_input: dict[str, Any] | None = None,
|
||||
) -> ConfigFlowResult:
|
||||
"""Ask whether to apply the changed create_cost_sensors setting to existing GUI sensors."""
|
||||
|
||||
if user_input is not None:
|
||||
if user_input.get(CONF_APPLY_TO_ALL):
|
||||
apply_field_to_config_entries(
|
||||
self.flow.hass,
|
||||
CONF_CREATE_COST_SENSOR,
|
||||
bool(self.flow.global_config.get(CONF_CREATE_COST_SENSORS)),
|
||||
)
|
||||
# When cost sensors were just enabled but no price is configured yet, continue to the price step.
|
||||
if self.flow.global_config.get(CONF_CREATE_COST_SENSORS) and not self.is_energy_price_configured():
|
||||
return await self.async_step_global_configuration_cost()
|
||||
return self.flow.persist_config_entry()
|
||||
|
||||
return await self.flow.handle_form_step(
|
||||
PowercalcFormStep(
|
||||
step=Step.GLOBAL_CONFIGURATION_COST_APPLY,
|
||||
schema=SCHEMA_COST_APPLY,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class GlobalConfigurationConfigFlow(GlobalConfigurationFlow):
|
||||
def __init__(self, flow: PowercalcConfigFlow) -> None:
|
||||
@@ -287,7 +388,7 @@ class GlobalConfigurationConfigFlow(GlobalConfigurationFlow):
|
||||
self.flow.abort_if_unique_id_configured()
|
||||
|
||||
if user_input is not None:
|
||||
self.flow.global_config.update(user_input)
|
||||
self.flow.global_config.update(flatten_sections(user_input, SCHEMA_GLOBAL_CONFIGURATION))
|
||||
return await self.async_step_global_configuration_discovery()
|
||||
|
||||
return await self.flow.handle_form_step(
|
||||
@@ -317,6 +418,8 @@ class GlobalConfigurationOptionsFlow(GlobalConfigurationFlow):
|
||||
}
|
||||
if self.flow.global_config.get(CONF_CREATE_ENERGY_SENSORS):
|
||||
menu[Step.GLOBAL_CONFIGURATION_ENERGY] = "Energy options"
|
||||
if self.flow.global_config.get(CONF_CREATE_COST_SENSORS):
|
||||
menu[Step.GLOBAL_CONFIGURATION_COST] = "Cost options"
|
||||
if self.flow.global_config.get(CONF_CREATE_UTILITY_METERS):
|
||||
menu[Step.GLOBAL_CONFIGURATION_UTILITY_METER] = "Utility meter options"
|
||||
return menu
|
||||
@@ -325,7 +428,12 @@ class GlobalConfigurationOptionsFlow(GlobalConfigurationFlow):
|
||||
"""Handle the global configuration step."""
|
||||
|
||||
if user_input is not None:
|
||||
cost_sensors_before = bool(self.flow.global_config.get(CONF_CREATE_COST_SENSORS))
|
||||
merge_global_config(self.flow.global_config, user_input, SCHEMA_GLOBAL_CONFIGURATION)
|
||||
# When the create_cost_sensors toggle is flipped (either direction), offer to apply
|
||||
# the change to all existing GUI sensors in a dedicated step.
|
||||
if bool(self.flow.global_config.get(CONF_CREATE_COST_SENSORS)) != cost_sensors_before:
|
||||
return await self.async_step_global_configuration_cost_apply()
|
||||
return self.flow.persist_config_entry()
|
||||
|
||||
return await self.flow.handle_form_step(
|
||||
|
||||
@@ -15,6 +15,7 @@ from homeassistant.const import (
|
||||
Platform,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.data_entry_flow import section
|
||||
from homeassistant.helpers import selector
|
||||
from homeassistant.helpers.schema_config_entry_flow import SchemaFlowError
|
||||
from homeassistant.helpers.selector import TextSelector
|
||||
@@ -45,7 +46,12 @@ from custom_components.powercalc.const import (
|
||||
GroupType,
|
||||
SensorType,
|
||||
)
|
||||
from custom_components.powercalc.flow_helper.common import PowercalcFormStep, Step, fill_schema_defaults
|
||||
from custom_components.powercalc.flow_helper.common import (
|
||||
PowercalcFormStep,
|
||||
Step,
|
||||
fill_schema_defaults,
|
||||
flatten_sections,
|
||||
)
|
||||
from custom_components.powercalc.flow_helper.schema import SCHEMA_ENERGY_SENSOR_TOGGLE, SCHEMA_UTILITY_METER_TOGGLE
|
||||
from custom_components.powercalc.group_include.include import find_entities
|
||||
from custom_components.powercalc.sensors.group.config_entry_utils import get_group_entries
|
||||
@@ -58,6 +64,9 @@ if TYPE_CHECKING:
|
||||
# Constants
|
||||
UNIQUE_ID_TRACKED_UNTRACKED = "pc_tracked_untracked"
|
||||
|
||||
SECTION_GROUP_MEMBERS = "members"
|
||||
SECTION_GROUP_OPTIONS = "options"
|
||||
|
||||
# Schemas
|
||||
SCHEMA_GROUP = vol.Schema(
|
||||
{
|
||||
@@ -192,7 +201,10 @@ def create_schema_group_custom(
|
||||
config_entry: ConfigEntry | None = None,
|
||||
is_option_flow: bool = False,
|
||||
) -> vol.Schema:
|
||||
"""Create config schema for groups."""
|
||||
"""Create config schema for groups.
|
||||
|
||||
Presented in the GUI as two collapsible sections (members and options).
|
||||
"""
|
||||
member_sensors = [
|
||||
selector.SelectOptionDict(value=config_entry.entry_id, label=config_entry.title)
|
||||
for config_entry in hass.config_entries.async_entries(DOMAIN)
|
||||
@@ -208,7 +220,7 @@ def create_schema_group_custom(
|
||||
),
|
||||
)
|
||||
|
||||
schema = vol.Schema(
|
||||
members_schema = vol.Schema(
|
||||
{
|
||||
vol.Optional(CONF_GROUP_MEMBER_SENSORS): member_sensor_selector,
|
||||
vol.Optional(CONF_GROUP_MEMBER_DEVICES): selector.DeviceSelector(
|
||||
@@ -236,6 +248,11 @@ def create_schema_group_custom(
|
||||
vol.Optional(CONF_SUB_GROUPS): create_group_selector(hass, current_entry=config_entry),
|
||||
vol.Optional(CONF_AREA): selector.AreaSelector(),
|
||||
vol.Optional(CONF_FLOOR): selector.FloorSelector(),
|
||||
},
|
||||
)
|
||||
|
||||
options_schema = vol.Schema(
|
||||
{
|
||||
vol.Optional(CONF_DEVICE): selector.DeviceSelector(),
|
||||
vol.Optional(CONF_HIDE_MEMBERS, default=False): selector.BooleanSelector(),
|
||||
vol.Optional(CONF_INCLUDE_NON_POWERCALC_SENSORS, default=True): selector.BooleanSelector(),
|
||||
@@ -244,7 +261,7 @@ def create_schema_group_custom(
|
||||
)
|
||||
|
||||
if not is_option_flow:
|
||||
schema = schema.extend(
|
||||
options_schema = options_schema.extend(
|
||||
{
|
||||
vol.Optional(CONF_GROUP_ENERGY_START_AT_ZERO, default=True): selector.BooleanSelector(),
|
||||
**SCHEMA_ENERGY_SENSOR_TOGGLE.schema,
|
||||
@@ -252,7 +269,12 @@ def create_schema_group_custom(
|
||||
},
|
||||
)
|
||||
|
||||
return schema
|
||||
return vol.Schema(
|
||||
{
|
||||
vol.Required(SECTION_GROUP_MEMBERS): section(members_schema),
|
||||
vol.Required(SECTION_GROUP_OPTIONS): section(options_schema),
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def create_group_selector(
|
||||
@@ -388,7 +410,11 @@ class GroupConfigFlow(GroupFlow):
|
||||
schema: vol.Schema | None = None,
|
||||
next_step: Callable[[dict[str, Any]], Step | None] | None = None,
|
||||
) -> ConfigFlowResult:
|
||||
resolved_schema = schema or GROUP_SCHEMAS[group_type]
|
||||
|
||||
def _validate(ui: dict[str, Any]) -> dict[str, Any]:
|
||||
# Flatten collapsible sections (e.g. the custom group members/options) back to flat keys.
|
||||
ui = flatten_sections(ui, resolved_schema)
|
||||
if group_type == GroupType.CUSTOM:
|
||||
validate_group_input(ui)
|
||||
|
||||
@@ -403,7 +429,7 @@ class GroupConfigFlow(GroupFlow):
|
||||
return await self.flow.handle_form_step(
|
||||
PowercalcFormStep(
|
||||
step=step,
|
||||
schema=schema or GROUP_SCHEMAS[group_type],
|
||||
schema=resolved_schema,
|
||||
validate_user_input=_validate,
|
||||
continue_utility_meter_options_step=True,
|
||||
next_step=next_step,
|
||||
@@ -412,7 +438,8 @@ class GroupConfigFlow(GroupFlow):
|
||||
)
|
||||
|
||||
async def async_step_group_custom(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult:
|
||||
schema = SCHEMA_GROUP.extend(create_schema_group_custom(self.flow.hass).schema)
|
||||
# Keep the name at top level; the remaining fields are grouped into collapsible sections.
|
||||
schema = vol.Schema({vol.Required(CONF_NAME): str}).extend(create_schema_group_custom(self.flow.hass).schema)
|
||||
return await self.handle_group_step(GroupType.CUSTOM, user_input, schema)
|
||||
|
||||
async def async_step_group_domain(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult:
|
||||
|
||||
@@ -47,8 +47,6 @@ from custom_components.powercalc.flow_helper.common import (
|
||||
PowercalcFormStep,
|
||||
Step,
|
||||
fill_schema_defaults,
|
||||
unwrap_choose_selector,
|
||||
wrap_choose_selector,
|
||||
)
|
||||
from custom_components.powercalc.flow_helper.flows.global_configuration import get_global_powercalc_config
|
||||
from custom_components.powercalc.flow_helper.flows.library import (
|
||||
@@ -61,6 +59,13 @@ from custom_components.powercalc.flow_helper.schema import (
|
||||
SCHEMA_SENSOR_ENERGY_OPTIONS,
|
||||
SCHEMA_UTILITY_METER_TOGGLE,
|
||||
)
|
||||
from custom_components.powercalc.flow_helper.strategy_form import (
|
||||
FIXED_CHOICES,
|
||||
find_present_choice,
|
||||
order_choices_for_default,
|
||||
unwrap_strategy_user_input,
|
||||
wrap_strategy_form_data,
|
||||
)
|
||||
from custom_components.powercalc.power_profile.power_profile import DeviceType
|
||||
from custom_components.powercalc.strategy.wled import CONFIG_SCHEMA as SCHEMA_POWER_WLED
|
||||
|
||||
@@ -110,28 +115,6 @@ FIXED_CHOICE_SELECTORS: dict[str, selector.ChooseSelectorChoiceConfig] = {
|
||||
}
|
||||
|
||||
|
||||
def order_choices_for_default(
|
||||
choices: dict[str, selector.ChooseSelectorChoiceConfig],
|
||||
default_choice: str | None,
|
||||
) -> dict[str, selector.ChooseSelectorChoiceConfig]:
|
||||
"""Put the default choice first because HA initializes choose selectors from the first choice."""
|
||||
if default_choice not in choices:
|
||||
return choices
|
||||
return {
|
||||
default_choice: choices[default_choice],
|
||||
**{choice: config for choice, config in choices.items() if choice != default_choice},
|
||||
}
|
||||
|
||||
|
||||
def find_present_choice(form_data: dict[str, Any], choices: dict[str, list[str] | str]) -> str | None:
|
||||
"""Find the first choice that has matching config data."""
|
||||
for choice_id, mapping in choices.items():
|
||||
keys = [mapping] if isinstance(mapping, str) else mapping
|
||||
if any(key in form_data for key in keys):
|
||||
return choice_id
|
||||
return None
|
||||
|
||||
|
||||
SCHEMA_POWER_FIXED = vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_FIXED_VALUE): selector.ChooseSelector(
|
||||
@@ -168,48 +151,6 @@ SCHEMA_POWER_LINEAR = vol.Schema(
|
||||
},
|
||||
)
|
||||
|
||||
FIXED_CHOICES: dict[str, list[str] | str] = {
|
||||
CONF_STATES_POWER: CONF_STATES_POWER,
|
||||
CONF_POWER_TEMPLATE: CONF_POWER_TEMPLATE,
|
||||
CONF_POWER: CONF_POWER,
|
||||
}
|
||||
|
||||
|
||||
def fixed_choice_key_from_validated_value(value: object) -> str:
|
||||
"""Infer the fixed strategy config key from a validated ChooseSelector value."""
|
||||
if isinstance(value, list):
|
||||
return CONF_STATES_POWER
|
||||
if isinstance(value, str):
|
||||
return CONF_POWER_TEMPLATE
|
||||
return CONF_POWER
|
||||
|
||||
|
||||
def unwrap_strategy_user_input(strategy: CalculationStrategy, user_input: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Unwrap ChooseSelector wrappers and normalize list/dict shapes for strategy user input."""
|
||||
if strategy == CalculationStrategy.FIXED:
|
||||
unwrap_choose_selector(user_input, CONF_FIXED_VALUE, fixed_choice_key_from_validated_value)
|
||||
if CONF_STATE_TRIGGER in user_input and isinstance(user_input[CONF_STATE_TRIGGER], list):
|
||||
user_input[CONF_STATE_TRIGGER] = {
|
||||
item[CONF_STATE]: item[CONF_PLAYBOOK_ID] for item in user_input[CONF_STATE_TRIGGER]
|
||||
}
|
||||
return user_input
|
||||
|
||||
|
||||
def wrap_strategy_form_data(strategy: CalculationStrategy, form_data: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Wrap flat strategy config back into ChooseSelector form structure for display."""
|
||||
if strategy == CalculationStrategy.FIXED:
|
||||
form_data = wrap_choose_selector(form_data, CONF_FIXED_VALUE, FIXED_CHOICES, raw_value=True)
|
||||
if CONF_STATE_TRIGGER in form_data and isinstance(form_data[CONF_STATE_TRIGGER], dict):
|
||||
form_data = {
|
||||
**form_data,
|
||||
CONF_STATE_TRIGGER: [
|
||||
{CONF_STATE: state, CONF_PLAYBOOK_ID: playbook_id}
|
||||
for state, playbook_id in form_data[CONF_STATE_TRIGGER].items()
|
||||
],
|
||||
}
|
||||
return form_data
|
||||
|
||||
|
||||
SCHEMA_POWER_MULTI_SWITCH_MANUAL = vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_POWER): vol.Coerce(float),
|
||||
|
||||
Reference in New Issue
Block a user