Updated apps

This commit is contained in:
2026-07-20 22:52:35 -04:00
parent 28a8cb98f6
commit a0c3271743
1164 changed files with 94781 additions and 6892 deletions
+7 -25
View File
@@ -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."""
@@ -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
@@ -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
@@ -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)),
+1 -1
View File
@@ -22,5 +22,5 @@
"requirements": [
"numpy>=1.21.1"
],
"version": "v1.22.0"
"version": "v1.23.0"
}
@@ -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 []
+30 -60
View File
@@ -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,
@@ -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)