updated apps
This commit is contained in:
1 parent
6cc7212cef
commit
010e828e9c
797 files changed
+45153
-4246
No files matched your search
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,130 @@
|
||||
"""Convert config entry data to runtime sensor configuration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
from datetime import timedelta
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_ENTITY_ID, CONF_NAME
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.template import Template
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from custom_components.powercalc.configuration.normalization import normalize_playbooks, normalize_states_power
|
||||
from custom_components.powercalc.const import (
|
||||
CONF_CALCULATION_ENABLED_CONDITION,
|
||||
CONF_CREATE_GROUP,
|
||||
CONF_DAILY_FIXED_ENERGY,
|
||||
CONF_FIXED,
|
||||
CONF_FORCE_ENERGY_SENSOR_CREATION,
|
||||
CONF_LINEAR,
|
||||
CONF_ON_TIME,
|
||||
CONF_PLAYBOOK,
|
||||
CONF_PLAYBOOKS,
|
||||
CONF_POWER,
|
||||
CONF_POWER_SENSOR_ID,
|
||||
CONF_POWER_TEMPLATE,
|
||||
CONF_SENSOR_TYPE,
|
||||
CONF_STATES_POWER,
|
||||
CONF_UTILITY_METER_OFFSET,
|
||||
CONF_VALUE,
|
||||
CONF_VALUE_TEMPLATE,
|
||||
SensorType,
|
||||
)
|
||||
|
||||
|
||||
def convert_config_entry_to_sensor_config(config_entry: ConfigEntry, hass: HomeAssistant) -> ConfigType: # noqa: C901
|
||||
"""Convert the config entry structure to the sensor config used to create the entities."""
|
||||
sensor_config = dict(config_entry.data.copy())
|
||||
sensor_type = sensor_config.get(CONF_SENSOR_TYPE)
|
||||
|
||||
def handle_sensor_type() -> None:
|
||||
"""Handle sensor type-specific configuration."""
|
||||
if sensor_type == SensorType.GROUP:
|
||||
sensor_config[CONF_CREATE_GROUP] = sensor_config.get(CONF_NAME)
|
||||
elif sensor_type == SensorType.REAL_POWER:
|
||||
sensor_config[CONF_POWER_SENSOR_ID] = sensor_config.get(CONF_ENTITY_ID)
|
||||
sensor_config[CONF_FORCE_ENERGY_SENSOR_CREATION] = True
|
||||
|
||||
def process_template(config: dict[str, Any], template_key: str, target_key: str) -> None:
|
||||
"""Convert a template key in the config to a Template object."""
|
||||
if template_key in config:
|
||||
config[target_key] = Template(config[template_key], hass)
|
||||
del config[template_key]
|
||||
|
||||
def process_on_time(config: dict[str, Any]) -> None:
|
||||
"""Convert on_time dictionary to timedelta."""
|
||||
on_time = config.get(CONF_ON_TIME)
|
||||
config[CONF_ON_TIME] = (
|
||||
timedelta(hours=on_time["hours"], minutes=on_time["minutes"], seconds=on_time["seconds"])
|
||||
if on_time
|
||||
else timedelta(days=1)
|
||||
)
|
||||
|
||||
def process_states_power(states_power: dict[str, Any] | list[dict[str, Any]]) -> dict[str, Any]:
|
||||
"""Convert state power values to Template objects where necessary."""
|
||||
return {
|
||||
key: Template(value, hass) if isinstance(value, str) and "{{" in value else value
|
||||
for key, value in normalize_states_power(states_power).items()
|
||||
}
|
||||
|
||||
def process_daily_fixed_energy() -> None:
|
||||
"""Process daily fixed energy configuration."""
|
||||
if CONF_DAILY_FIXED_ENERGY not in sensor_config:
|
||||
return
|
||||
|
||||
daily_fixed_config = copy.copy(sensor_config[CONF_DAILY_FIXED_ENERGY])
|
||||
process_template(daily_fixed_config, CONF_VALUE_TEMPLATE, CONF_VALUE)
|
||||
process_on_time(daily_fixed_config)
|
||||
sensor_config[CONF_DAILY_FIXED_ENERGY] = daily_fixed_config
|
||||
|
||||
def process_fixed_config() -> None:
|
||||
"""Process fixed energy configuration."""
|
||||
if CONF_FIXED not in sensor_config:
|
||||
return
|
||||
|
||||
fixed_config = copy.copy(sensor_config[CONF_FIXED])
|
||||
process_template(fixed_config, CONF_POWER_TEMPLATE, CONF_POWER)
|
||||
if CONF_STATES_POWER in fixed_config:
|
||||
fixed_config[CONF_STATES_POWER] = process_states_power(fixed_config[CONF_STATES_POWER])
|
||||
sensor_config[CONF_FIXED] = fixed_config
|
||||
|
||||
def process_linear_config() -> None:
|
||||
"""Process linear energy configuration."""
|
||||
if CONF_LINEAR not in sensor_config:
|
||||
return
|
||||
|
||||
linear_config = copy.copy(sensor_config[CONF_LINEAR])
|
||||
sensor_config[CONF_LINEAR] = linear_config
|
||||
|
||||
def process_calculation_enabled_condition() -> None:
|
||||
"""Process calculation enabled condition template."""
|
||||
if CONF_CALCULATION_ENABLED_CONDITION in sensor_config:
|
||||
sensor_config[CONF_CALCULATION_ENABLED_CONDITION] = Template(
|
||||
sensor_config[CONF_CALCULATION_ENABLED_CONDITION],
|
||||
hass,
|
||||
)
|
||||
|
||||
def process_utility_meter_offset() -> None:
|
||||
if CONF_UTILITY_METER_OFFSET in sensor_config:
|
||||
sensor_config[CONF_UTILITY_METER_OFFSET] = timedelta(days=sensor_config[CONF_UTILITY_METER_OFFSET])
|
||||
|
||||
def process_playbook_config() -> None:
|
||||
if CONF_PLAYBOOK not in sensor_config:
|
||||
return
|
||||
playbook_config = copy.copy(sensor_config[CONF_PLAYBOOK])
|
||||
playbook_config[CONF_PLAYBOOKS] = normalize_playbooks(playbook_config[CONF_PLAYBOOKS])
|
||||
sensor_config[CONF_PLAYBOOK] = playbook_config
|
||||
|
||||
handle_sensor_type()
|
||||
|
||||
process_daily_fixed_energy()
|
||||
process_fixed_config()
|
||||
process_linear_config()
|
||||
process_playbook_config()
|
||||
process_calculation_enabled_condition()
|
||||
process_utility_meter_offset()
|
||||
|
||||
return sensor_config
|
||||
@@ -0,0 +1,34 @@
|
||||
"""Normalize discovery info into sensor configuration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from homeassistant.const import CONF_ENTITY_ID
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
|
||||
from custom_components.powercalc.const import (
|
||||
CONF_GROUP_TYPE,
|
||||
CONF_SENSOR_TYPE,
|
||||
DISCOVERY_TYPE,
|
||||
DUMMY_ENTITY_ID,
|
||||
GroupType,
|
||||
PowercalcDiscoveryType,
|
||||
SensorType,
|
||||
)
|
||||
|
||||
GROUP_DISCOVERY_TYPES = {
|
||||
PowercalcDiscoveryType.DOMAIN_GROUP: GroupType.DOMAIN,
|
||||
PowercalcDiscoveryType.STANDBY_GROUP: GroupType.STANDBY,
|
||||
}
|
||||
|
||||
|
||||
def convert_discovery_info_to_sensor_config(
|
||||
discovery_info: DiscoveryInfoType,
|
||||
) -> ConfigType:
|
||||
"""Convert discovery info to sensor config."""
|
||||
group_type = GROUP_DISCOVERY_TYPES.get(discovery_info[DISCOVERY_TYPE])
|
||||
if group_type:
|
||||
discovery_info[CONF_GROUP_TYPE] = group_type
|
||||
discovery_info[CONF_SENSOR_TYPE] = SensorType.GROUP
|
||||
discovery_info[CONF_ENTITY_ID] = DUMMY_ENTITY_ID
|
||||
|
||||
return discovery_info
|
||||
@@ -8,6 +8,8 @@ from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from custom_components.powercalc.const import (
|
||||
CONF_COST_SENSOR_NAMING,
|
||||
CONF_CREATE_COST_SENSORS,
|
||||
CONF_CREATE_DOMAIN_GROUPS,
|
||||
CONF_CREATE_ENERGY_SENSORS,
|
||||
CONF_CREATE_STANDBY_GROUP,
|
||||
@@ -32,6 +34,7 @@ from custom_components.powercalc.const import (
|
||||
CONF_POWER_SENSOR_PRECISION,
|
||||
CONF_UTILITY_METER_OFFSET,
|
||||
CONF_UTILITY_METER_TYPES,
|
||||
DEFAULT_COST_NAME_PATTERN,
|
||||
DEFAULT_ENERGY_INTEGRATION_METHOD,
|
||||
DEFAULT_ENERGY_NAME_PATTERN,
|
||||
DEFAULT_ENERGY_SENSOR_PRECISION,
|
||||
@@ -72,6 +75,8 @@ def get_global_configuration(hass: HomeAssistant, config: ConfigType) -> ConfigT
|
||||
CONF_IGNORE_UNAVAILABLE_STATE: False,
|
||||
CONF_CREATE_DOMAIN_GROUPS: [],
|
||||
CONF_CREATE_ENERGY_SENSORS: True,
|
||||
CONF_CREATE_COST_SENSORS: False,
|
||||
CONF_COST_SENSOR_NAMING: DEFAULT_COST_NAME_PATTERN,
|
||||
CONF_CREATE_STANDBY_GROUP: True,
|
||||
CONF_CREATE_UTILITY_METERS: False,
|
||||
CONF_DISCOVERY: {
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
"""Normalize persisted configuration shapes to runtime mappings."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.const import CONF_ID, CONF_PATH
|
||||
|
||||
from custom_components.powercalc.const import CONF_PLAYBOOK_ID, CONF_POWER, CONF_STATE
|
||||
|
||||
|
||||
def normalize_states_power(states_power: dict[str, Any] | list[dict[str, Any]]) -> dict[str, Any]:
|
||||
"""Normalize state-power config to the runtime mapping shape."""
|
||||
if isinstance(states_power, list):
|
||||
return {item[CONF_STATE]: item[CONF_POWER] for item in states_power}
|
||||
return dict(states_power)
|
||||
|
||||
|
||||
def normalize_playbooks(playbooks: dict[str, str] | list[dict[str, str]]) -> dict[str, str]:
|
||||
"""Normalize playbook config to the runtime id-path mapping shape."""
|
||||
if isinstance(playbooks, list):
|
||||
return {item[CONF_ID]: item[CONF_PATH] for item in playbooks}
|
||||
return dict(playbooks)
|
||||
|
||||
|
||||
def normalize_state_trigger(state_trigger: dict[str, str] | list[dict[str, str]]) -> dict[str, str]:
|
||||
"""Normalize playbook state-trigger config to the runtime state-playbook mapping shape."""
|
||||
if isinstance(state_trigger, list):
|
||||
return {item[CONF_STATE]: item[CONF_PLAYBOOK_ID] for item in state_trigger}
|
||||
return dict(state_trigger)
|
||||
@@ -0,0 +1,184 @@
|
||||
"""Sensor configuration schemas."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from homeassistant.components.sensor import PLATFORM_SCHEMA as SENSOR_PLATFORM_SCHEMA
|
||||
from homeassistant.components.utility_meter import max_28_days
|
||||
from homeassistant.components.utility_meter.const import METER_TYPES
|
||||
from homeassistant.const import CONF_ENTITIES, CONF_ENTITY_ID, CONF_NAME, CONF_UNIQUE_ID
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
import voluptuous as vol
|
||||
|
||||
from custom_components.powercalc.common import validate_name_pattern
|
||||
from custom_components.powercalc.const import (
|
||||
CONF_AND,
|
||||
CONF_AVAILABILITY_ENTITY,
|
||||
CONF_CALCULATION_ENABLED_CONDITION,
|
||||
CONF_COMPOSITE,
|
||||
CONF_COST,
|
||||
CONF_CREATE_COST_SENSOR,
|
||||
CONF_CREATE_ENERGY_SENSOR,
|
||||
CONF_CREATE_GROUP,
|
||||
CONF_CREATE_UTILITY_METERS,
|
||||
CONF_CUSTOM_MODEL_DIRECTORY,
|
||||
CONF_DAILY_FIXED_ENERGY,
|
||||
CONF_DELAY,
|
||||
CONF_DISABLE_STANDBY_POWER,
|
||||
CONF_ENERGY_FILTER_OUTLIER_ENABLED,
|
||||
CONF_ENERGY_FILTER_OUTLIER_MAX,
|
||||
CONF_ENERGY_INTEGRATION_METHOD,
|
||||
CONF_ENERGY_SENSOR_CATEGORY,
|
||||
CONF_ENERGY_SENSOR_ID,
|
||||
CONF_ENERGY_SENSOR_NAMING,
|
||||
CONF_ENERGY_SENSOR_UNIT_PREFIX,
|
||||
CONF_FILTER,
|
||||
CONF_FIXED,
|
||||
CONF_FORCE_CALCULATE_GROUP_ENERGY,
|
||||
CONF_FORCE_ENERGY_SENSOR_CREATION,
|
||||
CONF_GROUP_ENERGY_START_AT_ZERO,
|
||||
CONF_GROUP_TYPE,
|
||||
CONF_HIDE_MEMBERS,
|
||||
CONF_IGNORE_UNAVAILABLE_STATE,
|
||||
CONF_INCLUDE,
|
||||
CONF_INCLUDE_NON_POWERCALC_SENSORS,
|
||||
CONF_LINEAR,
|
||||
CONF_MANUFACTURER,
|
||||
CONF_MODE,
|
||||
CONF_MODEL,
|
||||
CONF_MULTI_SWITCH,
|
||||
CONF_MULTIPLY_FACTOR,
|
||||
CONF_MULTIPLY_FACTOR_STANDBY,
|
||||
CONF_NOT,
|
||||
CONF_OR,
|
||||
CONF_PLAYBOOK,
|
||||
CONF_POWER,
|
||||
CONF_POWER_SENSOR_CATEGORY,
|
||||
CONF_POWER_SENSOR_ID,
|
||||
CONF_POWER_SENSOR_NAMING,
|
||||
CONF_SLEEP_POWER,
|
||||
CONF_STANDBY_POWER,
|
||||
CONF_SUBTRACT_ENTITIES,
|
||||
CONF_UNAVAILABLE_POWER,
|
||||
CONF_UTILITY_METER_NET_CONSUMPTION,
|
||||
CONF_UTILITY_METER_OFFSET,
|
||||
CONF_UTILITY_METER_TARIFFS,
|
||||
CONF_UTILITY_METER_TYPES,
|
||||
CONF_VARIABLES,
|
||||
CONF_WLED,
|
||||
ENERGY_INTEGRATION_METHODS,
|
||||
ENTITY_CATEGORIES,
|
||||
CalculationStrategy,
|
||||
GroupType,
|
||||
UnitPrefix,
|
||||
)
|
||||
from custom_components.powercalc.group_include.filter import FILTER_CONFIG
|
||||
from custom_components.powercalc.sensors.daily_energy import DAILY_FIXED_ENERGY_SCHEMA
|
||||
from custom_components.powercalc.strategy.composite import CONFIG_SCHEMA as COMPOSITE_SCHEMA
|
||||
from custom_components.powercalc.strategy.fixed import CONFIG_SCHEMA as FIXED_SCHEMA
|
||||
from custom_components.powercalc.strategy.linear import CONFIG_SCHEMA as LINEAR_SCHEMA
|
||||
from custom_components.powercalc.strategy.multi_switch import CONFIG_SCHEMA as MULTI_SWITCH_SCHEMA
|
||||
from custom_components.powercalc.strategy.playbook import CONFIG_SCHEMA as PLAYBOOK_SCHEMA
|
||||
from custom_components.powercalc.strategy.wled import CONFIG_SCHEMA as WLED_SCHEMA
|
||||
|
||||
MAX_GROUP_NESTING_LEVEL = 5
|
||||
|
||||
SENSOR_CONFIG = {
|
||||
vol.Optional(CONF_NAME): cv.string,
|
||||
vol.Optional(CONF_ENTITY_ID): cv.entity_id,
|
||||
vol.Optional(CONF_AVAILABILITY_ENTITY): cv.entity_id,
|
||||
vol.Optional(CONF_UNIQUE_ID): cv.string,
|
||||
vol.Optional(CONF_MODEL): cv.string,
|
||||
vol.Optional(CONF_MANUFACTURER): cv.string,
|
||||
vol.Optional(CONF_MODE): vol.In([cls.value for cls in CalculationStrategy]),
|
||||
vol.Optional(CONF_STANDBY_POWER): vol.Any(vol.Coerce(float), cv.template),
|
||||
vol.Optional(CONF_DISABLE_STANDBY_POWER): cv.boolean,
|
||||
vol.Optional(CONF_CUSTOM_MODEL_DIRECTORY): cv.string,
|
||||
vol.Optional(CONF_POWER_SENSOR_ID): cv.entity_id,
|
||||
vol.Optional(CONF_COST): vol.Schema({vol.Required(CONF_ENERGY_SENSOR_ID): cv.entity_id}),
|
||||
vol.Optional(CONF_FORCE_ENERGY_SENSOR_CREATION): cv.boolean,
|
||||
vol.Optional(CONF_FORCE_CALCULATE_GROUP_ENERGY): cv.boolean,
|
||||
vol.Optional(CONF_FIXED): FIXED_SCHEMA,
|
||||
vol.Optional(CONF_LINEAR): LINEAR_SCHEMA,
|
||||
vol.Optional(CONF_MULTI_SWITCH): MULTI_SWITCH_SCHEMA,
|
||||
vol.Optional(CONF_WLED): WLED_SCHEMA,
|
||||
vol.Optional(CONF_PLAYBOOK): PLAYBOOK_SCHEMA,
|
||||
vol.Optional(CONF_DAILY_FIXED_ENERGY): DAILY_FIXED_ENERGY_SCHEMA,
|
||||
vol.Optional(CONF_CREATE_ENERGY_SENSOR): cv.boolean,
|
||||
vol.Optional(CONF_CREATE_COST_SENSOR): cv.boolean,
|
||||
vol.Optional(CONF_CREATE_UTILITY_METERS): cv.boolean,
|
||||
vol.Optional(CONF_UTILITY_METER_NET_CONSUMPTION): cv.boolean,
|
||||
vol.Optional(CONF_UTILITY_METER_TARIFFS): vol.All(cv.ensure_list, [cv.string]),
|
||||
vol.Optional(CONF_UTILITY_METER_TYPES): vol.All(cv.ensure_list, [vol.In(METER_TYPES)]),
|
||||
vol.Optional(CONF_UTILITY_METER_OFFSET): vol.All(cv.time_period, cv.positive_timedelta, max_28_days),
|
||||
vol.Optional(CONF_MULTIPLY_FACTOR): vol.Coerce(float),
|
||||
vol.Optional(CONF_MULTIPLY_FACTOR_STANDBY): cv.boolean,
|
||||
vol.Optional(CONF_POWER_SENSOR_NAMING): validate_name_pattern,
|
||||
vol.Optional(CONF_POWER_SENSOR_CATEGORY): vol.In(ENTITY_CATEGORIES),
|
||||
vol.Optional(CONF_ENERGY_SENSOR_ID): cv.entity_id,
|
||||
vol.Optional(CONF_ENERGY_SENSOR_NAMING): validate_name_pattern,
|
||||
vol.Optional(CONF_ENERGY_SENSOR_CATEGORY): vol.In(ENTITY_CATEGORIES),
|
||||
vol.Optional(CONF_ENERGY_INTEGRATION_METHOD): vol.In(ENERGY_INTEGRATION_METHODS),
|
||||
vol.Optional(CONF_ENERGY_FILTER_OUTLIER_ENABLED): cv.boolean,
|
||||
vol.Optional(CONF_ENERGY_FILTER_OUTLIER_MAX): cv.positive_int,
|
||||
vol.Optional(CONF_ENERGY_SENSOR_UNIT_PREFIX): vol.In([cls.value for cls in UnitPrefix]),
|
||||
vol.Optional(CONF_CREATE_GROUP): cv.string,
|
||||
vol.Optional(CONF_GROUP_ENERGY_START_AT_ZERO): cv.boolean,
|
||||
vol.Optional(CONF_GROUP_TYPE): vol.In([cls.value for cls in GroupType]),
|
||||
vol.Optional(CONF_SUBTRACT_ENTITIES): vol.All(cv.ensure_list, [cv.entity_id]),
|
||||
vol.Optional(CONF_HIDE_MEMBERS): cv.boolean,
|
||||
vol.Optional(CONF_INCLUDE): vol.Schema(
|
||||
{
|
||||
**FILTER_CONFIG.schema,
|
||||
vol.Optional(CONF_FILTER): vol.Schema(
|
||||
{
|
||||
**FILTER_CONFIG.schema,
|
||||
vol.Optional(CONF_OR): vol.All(cv.ensure_list, [FILTER_CONFIG]),
|
||||
vol.Optional(CONF_AND): vol.All(cv.ensure_list, [FILTER_CONFIG]),
|
||||
vol.Optional(CONF_NOT): vol.All(cv.ensure_list, [FILTER_CONFIG]),
|
||||
},
|
||||
),
|
||||
vol.Optional(CONF_INCLUDE_NON_POWERCALC_SENSORS, default=True): cv.boolean,
|
||||
},
|
||||
),
|
||||
vol.Optional(CONF_IGNORE_UNAVAILABLE_STATE): cv.boolean,
|
||||
vol.Optional(CONF_CALCULATION_ENABLED_CONDITION): cv.template,
|
||||
vol.Optional(CONF_SLEEP_POWER): vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_POWER): vol.Coerce(float),
|
||||
vol.Required(CONF_DELAY): cv.positive_int,
|
||||
},
|
||||
),
|
||||
vol.Optional(CONF_UNAVAILABLE_POWER): vol.Coerce(float),
|
||||
vol.Optional(CONF_COMPOSITE): COMPOSITE_SCHEMA,
|
||||
vol.Optional(CONF_VARIABLES): vol.Schema({cv.string: cv.string}),
|
||||
}
|
||||
|
||||
|
||||
def build_nested_configuration_schema(schema: dict, iteration: int = 0) -> dict:
|
||||
if iteration == MAX_GROUP_NESTING_LEVEL:
|
||||
return schema
|
||||
iteration += 1
|
||||
schema.update(
|
||||
{
|
||||
vol.Optional(CONF_ENTITIES): vol.All(
|
||||
cv.ensure_list,
|
||||
[build_nested_configuration_schema(schema.copy(), iteration)],
|
||||
),
|
||||
},
|
||||
)
|
||||
return schema
|
||||
|
||||
|
||||
SENSOR_CONFIG = build_nested_configuration_schema(SENSOR_CONFIG)
|
||||
|
||||
PLATFORM_SCHEMA = vol.All(
|
||||
cv.has_at_least_one_key(
|
||||
CONF_ENTITY_ID,
|
||||
CONF_POWER_SENSOR_ID,
|
||||
CONF_ENTITIES,
|
||||
CONF_INCLUDE,
|
||||
CONF_DAILY_FIXED_ENERGY,
|
||||
CONF_COST,
|
||||
),
|
||||
SENSOR_PLATFORM_SCHEMA.extend(SENSOR_CONFIG),
|
||||
)
|
||||
Reference in new issue
Block a user