Updated apps
This commit is contained in:
@@ -335,13 +335,7 @@ def register_services(hass: HomeAssistant) -> None:
|
||||
if DOMAIN in reload_config:
|
||||
for sensor_config in reload_config[DOMAIN].get(CONF_SENSORS, []):
|
||||
sensor_config.update({DISCOVERY_TYPE: PowercalcDiscoveryType.USER_YAML})
|
||||
await async_load_platform(
|
||||
hass,
|
||||
Platform.SENSOR,
|
||||
DOMAIN,
|
||||
sensor_config,
|
||||
reload_config,
|
||||
)
|
||||
await _async_load_yaml_sensor(hass, sensor_config, reload_config)
|
||||
|
||||
# Reload all config entries
|
||||
for entry in hass.config_entries.async_entries(DOMAIN):
|
||||
@@ -426,15 +420,7 @@ async def setup_yaml_sensors(
|
||||
"""Load secondary sensors after primary sensors."""
|
||||
await asyncio.gather(
|
||||
*(
|
||||
hass.async_create_task(
|
||||
async_load_platform(
|
||||
hass,
|
||||
Platform.SENSOR,
|
||||
DOMAIN,
|
||||
sensor_config,
|
||||
config,
|
||||
),
|
||||
)
|
||||
hass.async_create_task(_async_load_yaml_sensor(hass, sensor_config, config))
|
||||
for sensor_config in secondary_sensors
|
||||
),
|
||||
)
|
||||
@@ -443,20 +429,16 @@ async def setup_yaml_sensors(
|
||||
|
||||
await asyncio.gather(
|
||||
*(
|
||||
hass.async_create_task(
|
||||
async_load_platform(
|
||||
hass,
|
||||
Platform.SENSOR,
|
||||
DOMAIN,
|
||||
sensor_config,
|
||||
config,
|
||||
),
|
||||
)
|
||||
hass.async_create_task(_async_load_yaml_sensor(hass, sensor_config, config))
|
||||
for sensor_config in primary_sensors
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
async def _async_load_yaml_sensor(hass: HomeAssistant, sensor_config: ConfigType, config: ConfigType) -> None:
|
||||
await async_load_platform(hass, Platform.SENSOR, DOMAIN, sensor_config, config)
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Set up Powercalc integration from a config entry."""
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
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.
@@ -2,9 +2,7 @@
|
||||
|
||||
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
|
||||
@@ -35,96 +33,57 @@ from custom_components.powercalc.const import (
|
||||
)
|
||||
|
||||
|
||||
def convert_config_entry_to_sensor_config(config_entry: ConfigEntry, hass: HomeAssistant) -> ConfigType: # noqa: C901
|
||||
def _convert_template(config: ConfigType, source_key: str, target_key: str, hass: HomeAssistant) -> None:
|
||||
if source_key in config:
|
||||
config[target_key] = Template(config.pop(source_key), hass)
|
||||
|
||||
|
||||
def convert_config_entry_to_sensor_config(config_entry: ConfigEntry, hass: HomeAssistant) -> ConfigType:
|
||||
"""Convert the config entry structure to the sensor config used to create the entities."""
|
||||
sensor_config = dict(config_entry.data.copy())
|
||||
sensor_config = dict(config_entry.data)
|
||||
sensor_type = sensor_config.get(CONF_SENSOR_TYPE)
|
||||
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 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] = (
|
||||
if CONF_DAILY_FIXED_ENERGY in sensor_config:
|
||||
daily_fixed_config = dict(sensor_config[CONF_DAILY_FIXED_ENERGY])
|
||||
_convert_template(daily_fixed_config, CONF_VALUE_TEMPLATE, CONF_VALUE, hass)
|
||||
on_time = daily_fixed_config.get(CONF_ON_TIME)
|
||||
daily_fixed_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_FIXED in sensor_config:
|
||||
fixed_config = dict(sensor_config[CONF_FIXED])
|
||||
_convert_template(fixed_config, CONF_POWER_TEMPLATE, CONF_POWER, hass)
|
||||
if CONF_STATES_POWER in fixed_config:
|
||||
fixed_config[CONF_STATES_POWER] = process_states_power(fixed_config[CONF_STATES_POWER])
|
||||
fixed_config[CONF_STATES_POWER] = {
|
||||
key: Template(value, hass) if isinstance(value, str) and "{{" in value else value
|
||||
for key, value in normalize_states_power(fixed_config[CONF_STATES_POWER]).items()
|
||||
}
|
||||
sensor_config[CONF_FIXED] = fixed_config
|
||||
|
||||
def process_linear_config() -> None:
|
||||
"""Process linear energy configuration."""
|
||||
if CONF_LINEAR not in sensor_config:
|
||||
return
|
||||
if CONF_LINEAR in sensor_config:
|
||||
sensor_config[CONF_LINEAR] = dict(sensor_config[CONF_LINEAR])
|
||||
|
||||
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])
|
||||
if CONF_PLAYBOOK in sensor_config:
|
||||
playbook_config = dict(sensor_config[CONF_PLAYBOOK])
|
||||
playbook_config[CONF_PLAYBOOKS] = normalize_playbooks(playbook_config[CONF_PLAYBOOKS])
|
||||
sensor_config[CONF_PLAYBOOK] = playbook_config
|
||||
|
||||
handle_sensor_type()
|
||||
if CONF_CALCULATION_ENABLED_CONDITION in sensor_config:
|
||||
sensor_config[CONF_CALCULATION_ENABLED_CONDITION] = Template(
|
||||
sensor_config[CONF_CALCULATION_ENABLED_CONDITION],
|
||||
hass,
|
||||
)
|
||||
|
||||
process_daily_fixed_energy()
|
||||
process_fixed_config()
|
||||
process_linear_config()
|
||||
process_playbook_config()
|
||||
process_calculation_enabled_condition()
|
||||
process_utility_meter_offset()
|
||||
if CONF_UTILITY_METER_OFFSET in sensor_config:
|
||||
sensor_config[CONF_UTILITY_METER_OFFSET] = timedelta(days=sensor_config[CONF_UTILITY_METER_OFFSET])
|
||||
|
||||
return sensor_config
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
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.
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.
@@ -415,11 +415,10 @@ class GlobalConfigurationOptionsFlow(GlobalConfigurationFlow):
|
||||
Step.GLOBAL_CONFIGURATION: "Basic options",
|
||||
Step.GLOBAL_CONFIGURATION_DISCOVERY: "Discovery options",
|
||||
Step.GLOBAL_CONFIGURATION_THROTTLING: "Throttling options",
|
||||
Step.GLOBAL_CONFIGURATION_COST: "Cost options",
|
||||
}
|
||||
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
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -9,9 +9,13 @@ from homeassistant.helpers.entity_registry import RegistryEntry
|
||||
|
||||
from custom_components.powercalc.common import create_source_entity
|
||||
from custom_components.powercalc.const import (
|
||||
CONF_SENSOR_TYPE,
|
||||
DATA_CONFIGURED_ENTITIES,
|
||||
DATA_ENTITIES,
|
||||
DOMAIN,
|
||||
ENTRY_DATA_ENERGY_ENTITY,
|
||||
ENTRY_DATA_POWER_ENTITY,
|
||||
SensorType,
|
||||
)
|
||||
from custom_components.powercalc.discovery import get_power_profile_by_source_entity
|
||||
from custom_components.powercalc.power_profile.power_profile import SUPPORTED_DOMAINS
|
||||
@@ -53,7 +57,7 @@ async def find_entities(
|
||||
resolved_entities: list[Entity] = []
|
||||
discoverable_entities: list[str] = []
|
||||
|
||||
source_entities = get_filtered_entity_list(hass, _build_filter(entity_filter))
|
||||
source_entities = get_filtered_entity_list(hass, _build_filter(hass, entity_filter, include_non_powercalc))
|
||||
|
||||
if _LOGGER.isEnabledFor(logging.DEBUG): # pragma: no cover
|
||||
_LOGGER.debug("Source entities: %s", [entity.entity_id for entity in source_entities])
|
||||
@@ -71,9 +75,6 @@ async def find_entities(
|
||||
resolved_entities.append(existing)
|
||||
continue
|
||||
|
||||
if _should_skip_source_entity(source_entity, include_non_powercalc):
|
||||
continue
|
||||
|
||||
real_sensor = _create_real_sensor(source_entity)
|
||||
if real_sensor:
|
||||
resolved_entities.append(real_sensor)
|
||||
@@ -92,8 +93,31 @@ async def find_entities(
|
||||
return FindEntitiesResult(resolved_entities, discoverable_entities)
|
||||
|
||||
|
||||
def _should_skip_source_entity(source_entity: RegistryEntry, include_non_powercalc: bool) -> bool:
|
||||
return source_entity.domain == sensor.DOMAIN and source_entity.platform != DOMAIN and not include_non_powercalc
|
||||
def _is_source_entity_eligible(
|
||||
hass: HomeAssistant,
|
||||
source_entity: RegistryEntry,
|
||||
include_non_powercalc: bool,
|
||||
) -> bool:
|
||||
"""Return whether a registry entity is eligible for Powercalc discovery."""
|
||||
if source_entity.platform != DOMAIN:
|
||||
return include_non_powercalc or source_entity.domain != sensor.DOMAIN
|
||||
|
||||
# YAML-created Powercalc entities have no config entry and are classified by their runtime type later.
|
||||
if source_entity.config_entry_id is None:
|
||||
return True
|
||||
|
||||
config_entry = hass.config_entries.async_get_entry(source_entity.config_entry_id)
|
||||
if config_entry is None:
|
||||
return False
|
||||
|
||||
if config_entry.data.get(CONF_SENSOR_TYPE) == SensorType.GROUP:
|
||||
return False
|
||||
|
||||
main_entity_ids = {
|
||||
config_entry.data.get(ENTRY_DATA_POWER_ENTITY),
|
||||
config_entry.data.get(ENTRY_DATA_ENERGY_ENTITY),
|
||||
}
|
||||
return source_entity.entity_id in main_entity_ids
|
||||
|
||||
|
||||
def _create_real_sensor(source_entity: RegistryEntry) -> Entity | None:
|
||||
@@ -120,10 +144,15 @@ async def _is_discoverable_source_entity(hass: HomeAssistant, source_entity: Reg
|
||||
)
|
||||
|
||||
|
||||
def _build_filter(entity_filter: EntityFilter | None) -> EntityFilter:
|
||||
def _build_filter(
|
||||
hass: HomeAssistant,
|
||||
entity_filter: EntityFilter | None,
|
||||
include_non_powercalc: bool,
|
||||
) -> EntityFilter:
|
||||
base_filter = CompositeFilter(
|
||||
[
|
||||
DomainFilter(SUPPORTED_DOMAINS),
|
||||
LambdaFilter(lambda entity: _is_source_entity_eligible(hass, entity, include_non_powercalc)),
|
||||
LambdaFilter(lambda entity: entity.platform != "utility_meter"),
|
||||
LambdaFilter(lambda entity: not str(entity.unique_id).startswith("powercalc_standby_group")),
|
||||
LambdaFilter(lambda entity: "tracked_" not in str(entity.unique_id)),
|
||||
|
||||
@@ -22,5 +22,5 @@
|
||||
"requirements": [
|
||||
"numpy>=1.21.1"
|
||||
],
|
||||
"version": "v1.22.0"
|
||||
"version": "v1.23.0"
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -53,23 +53,30 @@ class SubProfileSelector:
|
||||
def _create_matcher(self, matcher_config: dict) -> SubProfileMatcher:
|
||||
"""Create a matcher from json config. Can be extended for more matchers in the future."""
|
||||
matcher_type: SubProfileMatcherType = matcher_config["type"]
|
||||
|
||||
matcher_classes: dict[SubProfileMatcherType, type[SubProfileMatcher]] = {
|
||||
SubProfileMatcherType.ATTRIBUTE: AttributeMatcher,
|
||||
SubProfileMatcherType.ENTITY_STATE: EntityStateMatcher,
|
||||
SubProfileMatcherType.ENTITY_ID: EntityIdMatcher,
|
||||
SubProfileMatcherType.ENTITY_REGISTRY: EntityRegistryMatcher,
|
||||
SubProfileMatcherType.INTEGRATION: IntegrationMatcher,
|
||||
SubProfileMatcherType.MODEL_ID: ModelIdMatcher,
|
||||
}
|
||||
if matcher_type not in matcher_classes:
|
||||
raise PowercalcSetupError(f"Unknown sub profile matcher type: {matcher_type}")
|
||||
|
||||
return matcher_classes[matcher_type].from_config(
|
||||
matcher_config,
|
||||
hass=self._hass,
|
||||
source_entity=self._source_entity,
|
||||
)
|
||||
match matcher_type:
|
||||
case SubProfileMatcherType.ATTRIBUTE:
|
||||
return AttributeMatcher(matcher_config["attribute"], matcher_config["map"])
|
||||
case SubProfileMatcherType.ENTITY_STATE:
|
||||
return EntityStateMatcher(
|
||||
self._hass,
|
||||
self._source_entity,
|
||||
matcher_config["entity_id"],
|
||||
matcher_config["map"],
|
||||
)
|
||||
case SubProfileMatcherType.ENTITY_ID:
|
||||
return EntityIdMatcher(matcher_config["pattern"], matcher_config["profile"])
|
||||
case SubProfileMatcherType.ENTITY_REGISTRY:
|
||||
return EntityRegistryMatcher(
|
||||
matcher_config["property"],
|
||||
matcher_config["value"],
|
||||
matcher_config["profile"],
|
||||
)
|
||||
case SubProfileMatcherType.INTEGRATION:
|
||||
return IntegrationMatcher(matcher_config["integration"], matcher_config["profile"])
|
||||
case SubProfileMatcherType.MODEL_ID:
|
||||
return ModelIdMatcher(matcher_config["model_id"], matcher_config["profile"])
|
||||
case _:
|
||||
raise PowercalcSetupError(f"Unknown sub profile matcher type: {matcher_type}")
|
||||
|
||||
|
||||
class SubProfileSelectConfig(NamedTuple):
|
||||
@@ -78,21 +85,12 @@ class SubProfileSelectConfig(NamedTuple):
|
||||
|
||||
|
||||
class SubProfileMatcher(Protocol):
|
||||
@classmethod
|
||||
def from_config(
|
||||
cls,
|
||||
config: dict,
|
||||
*,
|
||||
hass: HomeAssistant | None = None,
|
||||
source_entity: SourceEntity | None = None,
|
||||
) -> SubProfileMatcher:
|
||||
"""Create a matcher from a config dict."""
|
||||
|
||||
def match(self, entity_state: State, source_entity: SourceEntity) -> str | None:
|
||||
"""Returns a sub profile."""
|
||||
|
||||
def get_tracking_entities(self) -> list[str]:
|
||||
"""Get extra entities to track for state changes."""
|
||||
return []
|
||||
|
||||
|
||||
class EntityStateMatcher(SubProfileMatcher):
|
||||
@@ -119,17 +117,6 @@ class EntityStateMatcher(SubProfileMatcher):
|
||||
|
||||
return self._mapping.get(state.state)
|
||||
|
||||
@classmethod
|
||||
def from_config(
|
||||
cls,
|
||||
config: dict,
|
||||
*,
|
||||
hass: HomeAssistant | None = None,
|
||||
source_entity: SourceEntity | None = None,
|
||||
) -> EntityStateMatcher:
|
||||
assert hass is not None
|
||||
return cls(hass, source_entity, config["entity_id"], config["map"])
|
||||
|
||||
def get_tracking_entities(self) -> list[str]:
|
||||
return [self._entity_id]
|
||||
|
||||
@@ -146,19 +133,6 @@ class AttributeMatcher(SubProfileMatcher):
|
||||
|
||||
return self._mapping.get(val)
|
||||
|
||||
@classmethod
|
||||
def from_config(
|
||||
cls,
|
||||
config: dict,
|
||||
*,
|
||||
hass: HomeAssistant | None = None,
|
||||
source_entity: SourceEntity | None = None,
|
||||
) -> AttributeMatcher:
|
||||
return cls(config["attribute"], config["map"])
|
||||
|
||||
def get_tracking_entities(self) -> list[str]:
|
||||
return []
|
||||
|
||||
|
||||
class EntityIdMatcher(SubProfileMatcher):
|
||||
def __init__(self, pattern: str, profile: str) -> None:
|
||||
@@ -171,19 +145,6 @@ class EntityIdMatcher(SubProfileMatcher):
|
||||
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def from_config(
|
||||
cls,
|
||||
config: dict,
|
||||
*,
|
||||
hass: HomeAssistant | None = None,
|
||||
source_entity: SourceEntity | None = None,
|
||||
) -> EntityIdMatcher:
|
||||
return cls(config["pattern"], config["profile"])
|
||||
|
||||
def get_tracking_entities(self) -> list[str]:
|
||||
return []
|
||||
|
||||
|
||||
class IntegrationMatcher(SubProfileMatcher):
|
||||
def __init__(self, integration: str, profile: str) -> None:
|
||||
@@ -200,19 +161,6 @@ class IntegrationMatcher(SubProfileMatcher):
|
||||
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def from_config(
|
||||
cls,
|
||||
config: dict,
|
||||
*,
|
||||
hass: HomeAssistant | None = None,
|
||||
source_entity: SourceEntity | None = None,
|
||||
) -> IntegrationMatcher:
|
||||
return cls(config["integration"], config["profile"])
|
||||
|
||||
def get_tracking_entities(self) -> list[str]:
|
||||
return []
|
||||
|
||||
|
||||
class EntityRegistryMatcher(SubProfileMatcher):
|
||||
def __init__(self, property_name: str, value: object, profile: str) -> None:
|
||||
@@ -234,19 +182,6 @@ class EntityRegistryMatcher(SubProfileMatcher):
|
||||
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def from_config(
|
||||
cls,
|
||||
config: dict,
|
||||
*,
|
||||
hass: HomeAssistant | None = None,
|
||||
source_entity: SourceEntity | None = None,
|
||||
) -> EntityRegistryMatcher:
|
||||
return cls(config["property"], config["value"], config["profile"])
|
||||
|
||||
def get_tracking_entities(self) -> list[str]:
|
||||
return []
|
||||
|
||||
def _matches_registry_value(self, registry_value: object) -> bool:
|
||||
if registry_value == self._value:
|
||||
return True
|
||||
@@ -271,16 +206,3 @@ class ModelIdMatcher(SubProfileMatcher):
|
||||
return self._profile
|
||||
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def from_config(
|
||||
cls,
|
||||
config: dict,
|
||||
*,
|
||||
hass: HomeAssistant | None = None,
|
||||
source_entity: SourceEntity | None = None,
|
||||
) -> ModelIdMatcher:
|
||||
return cls(config["model_id"], config["profile"])
|
||||
|
||||
def get_tracking_entities(self) -> list[str]:
|
||||
return []
|
||||
|
||||
@@ -657,7 +657,7 @@ async def create_individual_sensors(
|
||||
collect_sensor_analytics(hass, sensor_type, context.discovery_type, config_entry)
|
||||
|
||||
entities_to_add: list[Entity] = []
|
||||
energy_sensor = await handle_energy_sensor_creation(hass, sensor_config, source_entity, entities_to_add)
|
||||
energy_sensor = await _create_daily_fixed_energy_sensors(hass, sensor_config, source_entity, entities_to_add)
|
||||
|
||||
if not energy_sensor:
|
||||
try:
|
||||
@@ -665,10 +665,15 @@ async def create_individual_sensors(
|
||||
except PowercalcSetupError:
|
||||
return EntitiesBucket()
|
||||
entities_to_add.append(power_sensor)
|
||||
energy_sensor = create_energy_sensor_if_needed(hass, sensor_config, power_sensor, source_entity)
|
||||
if energy_sensor:
|
||||
if (
|
||||
sensor_config.get(CONF_CREATE_ENERGY_SENSOR)
|
||||
or sensor_config.get(CONF_FORCE_ENERGY_SENSOR_CREATION)
|
||||
or CONF_ENERGY_SENSOR_ID in sensor_config
|
||||
):
|
||||
energy_sensor = create_energy_sensor(hass, sensor_config, power_sensor, source_entity)
|
||||
entities_to_add.append(energy_sensor)
|
||||
attach_energy_sensor_to_power_sensor(power_sensor, energy_sensor)
|
||||
if isinstance(power_sensor, VirtualPowerSensor):
|
||||
power_sensor.set_energy_sensor_attribute(energy_sensor.entity_id)
|
||||
|
||||
if energy_sensor:
|
||||
entities_to_add.extend(
|
||||
@@ -676,7 +681,10 @@ async def create_individual_sensors(
|
||||
)
|
||||
|
||||
await attach_entities_to_resolved_device(config_entry, entities_to_add, hass, source_entity, sensor_config)
|
||||
update_registries(hass, source_entity, entities_to_add, context)
|
||||
hass.data[DOMAIN][DATA_CONFIGURED_ENTITIES].update(
|
||||
{source_entity.entity_id: [(entity, context.is_yaml) for entity in entities_to_add]},
|
||||
)
|
||||
hass.data[DOMAIN][DATA_DOMAIN_ENTITIES].setdefault(source_entity.domain, []).extend(entities_to_add)
|
||||
|
||||
unique_id = sensor_config.get(CONF_UNIQUE_ID) or source_entity.unique_id
|
||||
if unique_id:
|
||||
@@ -687,6 +695,23 @@ async def create_individual_sensors(
|
||||
return EntitiesBucket(new=entities_to_add, existing=[])
|
||||
|
||||
|
||||
async def _create_daily_fixed_energy_sensors(
|
||||
hass: HomeAssistant,
|
||||
sensor_config: dict,
|
||||
source_entity: SourceEntity,
|
||||
entities_to_add: list[Entity],
|
||||
) -> EnergySensor | None:
|
||||
if CONF_DAILY_FIXED_ENERGY not in sensor_config:
|
||||
return None
|
||||
|
||||
energy_sensor = create_daily_fixed_energy_sensor(hass, sensor_config, source_entity)
|
||||
entities_to_add.append(energy_sensor)
|
||||
power_sensor = await create_daily_fixed_energy_power_sensor(hass, sensor_config, source_entity)
|
||||
if power_sensor:
|
||||
entities_to_add.append(power_sensor)
|
||||
return energy_sensor
|
||||
|
||||
|
||||
def _attach_configured_device_entry(
|
||||
hass: HomeAssistant,
|
||||
sensor_config: dict,
|
||||
@@ -702,61 +727,6 @@ def _attach_configured_device_entry(
|
||||
return source_entity
|
||||
|
||||
|
||||
async def handle_energy_sensor_creation(
|
||||
hass: HomeAssistant,
|
||||
sensor_config: dict,
|
||||
source_entity: SourceEntity,
|
||||
entities_to_add: list[Entity],
|
||||
) -> EnergySensor | None:
|
||||
"""Handle the creation of an energy sensor if needed."""
|
||||
if CONF_DAILY_FIXED_ENERGY in sensor_config:
|
||||
energy_sensor = create_daily_fixed_energy_sensor(hass, sensor_config, source_entity)
|
||||
entities_to_add.append(energy_sensor)
|
||||
if source_entity:
|
||||
daily_fixed_power_sensor = await create_daily_fixed_energy_power_sensor(hass, sensor_config, source_entity)
|
||||
if daily_fixed_power_sensor:
|
||||
entities_to_add.append(daily_fixed_power_sensor)
|
||||
return energy_sensor
|
||||
return None
|
||||
|
||||
|
||||
def create_energy_sensor_if_needed(
|
||||
hass: HomeAssistant,
|
||||
sensor_config: dict,
|
||||
power_sensor: PowerSensor,
|
||||
source_entity: SourceEntity,
|
||||
) -> EnergySensor | None:
|
||||
"""Create an energy sensor if it is needed."""
|
||||
if (
|
||||
sensor_config.get(CONF_CREATE_ENERGY_SENSOR)
|
||||
or sensor_config.get(CONF_FORCE_ENERGY_SENSOR_CREATION)
|
||||
or CONF_ENERGY_SENSOR_ID in sensor_config
|
||||
):
|
||||
return create_energy_sensor(hass, sensor_config, power_sensor, source_entity)
|
||||
return None
|
||||
|
||||
|
||||
def attach_energy_sensor_to_power_sensor(power_sensor: Entity, energy_sensor: EnergySensor) -> None:
|
||||
"""Attach the energy sensor to the power sensor."""
|
||||
if isinstance(power_sensor, VirtualPowerSensor):
|
||||
power_sensor.set_energy_sensor_attribute(energy_sensor.entity_id)
|
||||
|
||||
|
||||
def update_registries(
|
||||
hass: HomeAssistant,
|
||||
source_entity: SourceEntity,
|
||||
entities_to_add: list[Entity],
|
||||
creation_context: CreationContext,
|
||||
) -> None:
|
||||
"""Update various registries with the new entities."""
|
||||
hass.data[DOMAIN][DATA_CONFIGURED_ENTITIES].update(
|
||||
{source_entity.entity_id: [(entity, creation_context.is_yaml) for entity in entities_to_add]},
|
||||
)
|
||||
|
||||
domain_entities = hass.data[DOMAIN][DATA_DOMAIN_ENTITIES].setdefault(source_entity.domain, [])
|
||||
domain_entities.extend(entities_to_add)
|
||||
|
||||
|
||||
def check_entity_not_already_configured(
|
||||
sensor_config: dict,
|
||||
source_entity: SourceEntity,
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
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.
Binary file not shown.
BIN
Binary file not shown.
@@ -474,7 +474,6 @@ class GroupedSensor(BaseEntity, SensorEntity):
|
||||
self._start_time: float = time.time()
|
||||
self._last_update_time: float = 0
|
||||
self._update_interval_exceeded_callback: CALLBACK_TYPE | None = None
|
||||
self._unit_converter_cache: dict[str, Callable[[float], float]] = {}
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Register state listeners."""
|
||||
@@ -793,7 +792,6 @@ class GroupedEnergySensor(GroupedSensor, RestoreSensor, EnergySensor):
|
||||
"""Reset the group sensor and underlying member sensor when supported."""
|
||||
_LOGGER.debug("%s: Reset grouped energy sensor", self.entity_id)
|
||||
self._set_native_value(Decimal(0))
|
||||
self.async_write_ha_state()
|
||||
|
||||
for entity_id in self._entities:
|
||||
_LOGGER.debug("Resetting %s", entity_id)
|
||||
@@ -813,7 +811,6 @@ class GroupedEnergySensor(GroupedSensor, RestoreSensor, EnergySensor):
|
||||
async def async_calibrate(self, value: str) -> None:
|
||||
_LOGGER.debug("%s: Calibrate group energy sensor to: %s", self.entity_id, value)
|
||||
self._set_native_value(Decimal(value))
|
||||
self.async_write_ha_state()
|
||||
|
||||
def calculate_initial_state(
|
||||
self,
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
from homeassistant.const import CONF_DOMAIN, CONF_NAME, CONF_UNIQUE_ID
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from custom_components.powercalc.const import CONF_GROUP_TYPE, GroupType
|
||||
from custom_components.powercalc.sensors.group.custom import create_group_sensors_custom
|
||||
|
||||
|
||||
def create_domain_group_sensor(
|
||||
hass: HomeAssistant,
|
||||
config: ConfigType,
|
||||
) -> list[Entity]:
|
||||
domain = config[CONF_DOMAIN]
|
||||
name: str = config.get(CONF_NAME, f"All {domain}")
|
||||
if CONF_UNIQUE_ID not in config:
|
||||
config[CONF_UNIQUE_ID] = generate_unique_id(config)
|
||||
config[CONF_GROUP_TYPE] = GroupType.DOMAIN
|
||||
return create_group_sensors_custom(
|
||||
hass,
|
||||
name,
|
||||
config,
|
||||
set(),
|
||||
set(),
|
||||
force_create=True,
|
||||
)
|
||||
|
||||
|
||||
def generate_unique_id(sensor_config: ConfigType) -> str:
|
||||
return f"powercalc_domaingroup_{sensor_config[CONF_DOMAIN]}"
|
||||
@@ -1,4 +1,5 @@
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_DOMAIN, CONF_NAME, CONF_UNIQUE_ID
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
@@ -7,7 +8,6 @@ from custom_components.powercalc.analytics.analytics import collect_analytics
|
||||
from custom_components.powercalc.const import CONF_GROUP_TYPE, DATA_GROUP_TYPES, GroupType
|
||||
from custom_components.powercalc.errors import SensorConfigurationError
|
||||
import custom_components.powercalc.sensors.group.custom as custom_group
|
||||
import custom_components.powercalc.sensors.group.domain as domain_group
|
||||
import custom_components.powercalc.sensors.group.standby as standby_group
|
||||
import custom_components.powercalc.sensors.group.subtract as subtract_group
|
||||
from custom_components.powercalc.sensors.group.tracked_untracked import TrackedPowerSensorFactory
|
||||
@@ -24,9 +24,17 @@ async def create_group_sensors(
|
||||
collect_analytics(hass, config_entry).inc(DATA_GROUP_TYPES, group_type)
|
||||
|
||||
if group_type == GroupType.DOMAIN:
|
||||
return domain_group.create_domain_group_sensor(
|
||||
domain = sensor_config[CONF_DOMAIN]
|
||||
name: str = sensor_config.get(CONF_NAME, f"All {domain}")
|
||||
sensor_config.setdefault(CONF_UNIQUE_ID, f"powercalc_domaingroup_{domain}")
|
||||
sensor_config[CONF_GROUP_TYPE] = GroupType.DOMAIN
|
||||
return custom_group.create_group_sensors_custom(
|
||||
hass,
|
||||
name,
|
||||
sensor_config,
|
||||
set(),
|
||||
set(),
|
||||
force_create=True,
|
||||
)
|
||||
if group_type == GroupType.STANDBY:
|
||||
return standby_group.create_general_standby_sensors(hass, sensor_config)
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user