224 files

This commit is contained in:
Home Assistant Version Control
2026-07-27 13:30:34 +00:00
parent b5723aa856
commit e36b8a1a22
224 changed files with 9967 additions and 2663 deletions
@@ -4,12 +4,13 @@ from collections.abc import Callable
from decimal import Decimal
from typing import Any, cast
from homeassistant.const import CONF_CONDITION, CONF_ENTITIES, CONF_ENTITY_ID
from homeassistant.const import CONF_CONDITION, CONF_CONDITIONS, CONF_ENTITIES, CONF_ENTITY_ID
from homeassistant.core import HomeAssistant
from homeassistant.helpers import condition
from homeassistant.helpers.singleton import singleton
from homeassistant.helpers.template import Template
from homeassistant.helpers.typing import ConfigType
import voluptuous as vol
from custom_components.powercalc.common import SourceEntity
from custom_components.powercalc.const import (
@@ -31,7 +32,14 @@ from custom_components.powercalc.errors import (
)
from custom_components.powercalc.power_profile.power_profile import PowerProfile
from .composite import DEFAULT_MODE, CompositeStrategy, SubStrategy
from .composite import (
COMPOUND_CONDITIONS,
CONFIG_SCHEMA as COMPOSITE_SCHEMA,
DEFAULT_MODE,
ENTITY_CONDITIONS,
CompositeStrategy,
SubStrategy,
)
from .fixed import FixedStrategy
from .linear import LinearStrategy
from .lut import LutRegistry, LutStrategy
@@ -42,6 +50,24 @@ from .strategy_interface import PowerCalculationStrategyInterface
from .wled import WledStrategy
def resolve_condition_entity_ids(condition_config: ConfigType, source_entity: SourceEntity) -> ConfigType:
"""Default entity_id to the source entity for conditions which omit it.
Recurses into and/or/not, so nested conditions are treated the same as top level ones.
Returns a copy, to prevent mutating the profile configuration.
"""
resolved = dict(condition_config)
condition_type = resolved.get(CONF_CONDITION)
if condition_type in COMPOUND_CONDITIONS:
resolved[CONF_CONDITIONS] = [
resolve_condition_entity_ids(sub_condition, source_entity)
for sub_condition in resolved.get(CONF_CONDITIONS, [])
]
elif condition_type in ENTITY_CONDITIONS and CONF_ENTITY_ID not in resolved:
resolved[CONF_ENTITY_ID] = [source_entity.entity_id]
return resolved
class PowerCalculatorStrategyFactory:
def __init__(self, hass: HomeAssistant) -> None:
self._hass = hass
@@ -167,7 +193,7 @@ class PowerCalculatorStrategyFactory:
composite_config: list | dict | None = config.get(CONF_COMPOSITE)
if composite_config is None:
if power_profile and power_profile.composite_config:
composite_config = power_profile.composite_config
composite_config = self._validate_composite_config(power_profile.composite_config)
else:
raise StrategyConfigurationError("No composite configuration supplied")
@@ -183,11 +209,8 @@ class PowerCalculatorStrategyFactory:
condition_instance = None
condition_config = strategy_config.get(CONF_CONDITION)
if condition_config:
condition_type = condition_config.get(CONF_CONDITION)
if condition_type in ["state", "numeric_state"] and CONF_ENTITY_ID not in condition_config:
condition_config[CONF_ENTITY_ID] = [source_entity.entity_id]
if condition_type == "state":
condition_config = condition.state_validate_config(self._hass, condition_config)
condition_config = resolve_condition_entity_ids(condition_config, source_entity)
condition_config = await condition.async_validate_condition_config(self._hass, condition_config)
condition_instance = await condition.async_from_config(
self._hass,
condition_config,
@@ -207,6 +230,19 @@ class PowerCalculatorStrategyFactory:
strategies = [await _create_sub_strategy(config) for config in sub_strategies]
return CompositeStrategy(self._hass, strategies, mode)
@staticmethod
def _validate_composite_config(composite_config: list | dict) -> list | dict:
"""Validate the composite configuration of a library profile.
Configuration from YAML and the config flow is already validated by the sensor schema.
Library profiles are raw JSON, so validate them here as well to get the same normalization,
for example entity_id to a list and value_template to a Template instance.
"""
try:
return cast(list | dict, COMPOSITE_SCHEMA(composite_config))
except vol.Invalid as err:
raise StrategyConfigurationError(f"Invalid composite configuration in profile: {err}") from err
def _create_multi_switch(self, config: ConfigType, power_profile: PowerProfile | None) -> MultiSwitchStrategy:
"""Create instance of multi switch strategy."""
multi_switch_config: ConfigType = {}