diff --git a/custom_components/powercalc/config_flow.py b/custom_components/powercalc/config_flow.py index 26a88a7da..df3648b8a 100644 --- a/custom_components/powercalc/config_flow.py +++ b/custom_components/powercalc/config_flow.py @@ -27,6 +27,7 @@ from .common import SourceEntity, create_source_entity from .const import ( + CONF_AREA, CONF_CALCULATION_ENABLED_CONDITION, CONF_CALIBRATE, CONF_CREATE_ENERGY_SENSOR, @@ -873,6 +874,7 @@ def _create_group_options_schema(hass: HomeAssistant) -> vol.Schema: ), ), vol.Optional(CONF_SUB_GROUPS): _create_group_selector(hass, multiple=True), + vol.Optional(CONF_AREA): selector.AreaSelector(), vol.Optional( CONF_CREATE_UTILITY_METERS, default=False, @@ -915,6 +917,7 @@ def _validate_group_input(user_input: dict[str, Any] | None = None) -> dict: and CONF_GROUP_POWER_ENTITIES not in user_input and CONF_GROUP_ENERGY_ENTITIES not in user_input and CONF_GROUP_MEMBER_SENSORS not in user_input + and CONF_AREA not in user_input ): errors["base"] = "group_mandatory" diff --git a/custom_components/powercalc/group_include/include.py b/custom_components/powercalc/group_include/include.py index 2af77a7d7..4be9ed26b 100644 --- a/custom_components/powercalc/group_include/include.py +++ b/custom_components/powercalc/group_include/include.py @@ -15,6 +15,8 @@ CONF_FILTER, CONF_GROUP, CONF_TEMPLATE, + DATA_CONFIGURED_ENTITIES, + DOMAIN, ) from custom_components.powercalc.errors import SensorConfigurationError @@ -23,8 +25,22 @@ _LOGGER = logging.getLogger(__name__) +def resolve_include_entities(hass: HomeAssistant, include_config: dict) -> list: + powercalc_entities = [] + source_entities = resolve_include_source_entities(hass, include_config) + _LOGGER.debug("Found include entities: %s", source_entities) + for source_entity in source_entities: + if source_entity.entity_id in hass.data[DOMAIN][DATA_CONFIGURED_ENTITIES]: + powercalc_entities.extend( + hass.data[DOMAIN][DATA_CONFIGURED_ENTITIES][ + source_entity.entity_id + ], + ) + return powercalc_entities + + @callback -def resolve_include_entities( +def resolve_include_source_entities( hass: HomeAssistant, include_config: dict, ) -> list[entity_registry.RegistryEntry]: diff --git a/custom_components/powercalc/sensor.py b/custom_components/powercalc/sensor.py index 0632f0c95..7a3b81c6d 100644 --- a/custom_components/powercalc/sensor.py +++ b/custom_components/powercalc/sensor.py @@ -557,7 +557,7 @@ def convert_config_entry_to_sensor_config(config_entry: ConfigEntry) -> ConfigTy return sensor_config -async def create_sensors( # noqa: C901 +async def create_sensors( hass: HomeAssistant, config: ConfigType, discovery_info: DiscoveryInfoType | None = None, @@ -616,15 +616,7 @@ async def create_sensors( # noqa: C901 # Automatically add a bunch of entities by area or evaluating template if CONF_INCLUDE in config: - include_entities = resolve_include_entities(hass, config.get(CONF_INCLUDE)) # type: ignore - _LOGGER.debug("Found include entities: %s", include_entities) - for source_entity in include_entities: - if source_entity.entity_id in hass.data[DOMAIN][DATA_CONFIGURED_ENTITIES]: - entities_to_add.existing.extend( - hass.data[DOMAIN][DATA_CONFIGURED_ENTITIES][ - source_entity.entity_id - ], - ) + entities_to_add.existing.extend(resolve_include_entities(hass, config.get(CONF_INCLUDE))) # type: ignore # Create sensors for each entity for sensor_config in sensor_configs.values(): diff --git a/custom_components/powercalc/sensors/group.py b/custom_components/powercalc/sensors/group.py index 7d7942697..1ea1a06b1 100644 --- a/custom_components/powercalc/sensors/group.py +++ b/custom_components/powercalc/sensors/group.py @@ -51,6 +51,7 @@ from custom_components.powercalc.const import ( ATTR_ENTITIES, ATTR_IS_GROUP, + CONF_AREA, CONF_DISABLE_EXTENDED_ATTRIBUTES, CONF_ENERGY_SENSOR_PRECISION, CONF_ENERGY_SENSOR_UNIT_PREFIX, @@ -71,6 +72,7 @@ SensorType, UnitPrefix, ) +from custom_components.powercalc.group_include.include import resolve_include_entities from .abstract import ( BaseEntity, @@ -168,8 +170,13 @@ async def create_group_sensors_from_config_entry( if CONF_UNIQUE_ID not in sensor_config: sensor_config[CONF_UNIQUE_ID] = entry.entry_id + area_entities: list[Entity] = [] + if CONF_AREA in entry.data: + area_entities = resolve_include_entities(hass, {CONF_AREA: entry.data[CONF_AREA]}) + power_sensor_ids: set[str] = set( - resolve_entity_ids_recursively(hass, entry, SensorDeviceClass.POWER), + resolve_entity_ids_recursively(hass, entry, SensorDeviceClass.POWER) + + [entity.entity_id for entity in area_entities if isinstance(entity, PowerSensor)], ) if power_sensor_ids: power_sensor = create_grouped_power_sensor( @@ -181,7 +188,8 @@ async def create_group_sensors_from_config_entry( group_sensors.append(power_sensor) energy_sensor_ids: set[str] = set( - resolve_entity_ids_recursively(hass, entry, SensorDeviceClass.ENERGY), + resolve_entity_ids_recursively(hass, entry, SensorDeviceClass.ENERGY) + + [entity.entity_id for entity in area_entities if isinstance(entity, EnergySensor)], ) if energy_sensor_ids: energy_sensor = create_grouped_energy_sensor( @@ -350,6 +358,7 @@ def resolve_entity_ids_recursively( _LOGGER.error(f"Subgroup config entry not found: {subgroup_entry_id}") continue resolve_entity_ids_recursively(hass, subgroup_entry, device_class, resolved_ids) + return resolved_ids diff --git a/custom_components/powercalc/strategy/factory.py b/custom_components/powercalc/strategy/factory.py index 983a6d66a..428bff550 100644 --- a/custom_components/powercalc/strategy/factory.py +++ b/custom_components/powercalc/strategy/factory.py @@ -153,7 +153,7 @@ async def _create_composite( power_profile: PowerProfile | None, source_entity: SourceEntity, ) -> CompositeStrategy: - sub_strategies = config.get(CONF_COMPOSITE) # type: ignore + sub_strategies = list(config.get(CONF_COMPOSITE)) # type: ignore async def _create_sub_strategy(strategy_config: ConfigType) -> SubStrategy: condition_instance = None diff --git a/custom_components/powercalc/strings.json b/custom_components/powercalc/strings.json index 49f99e618..0997c8463 100644 --- a/custom_components/powercalc/strings.json +++ b/custom_components/powercalc/strings.json @@ -37,6 +37,7 @@ "group_power_entities": "Additional power entities", "group_energy_entities": "Additional energy entities", "sub_groups": "Sub groups", + "area": "Area", "hide_members": "Hide members", "create_utility_meters": "[%key:component::powercalc::config::step::virtual_power::data::create_utility_meters%]" }, @@ -44,7 +45,8 @@ "group_member_sensors": "Powercalc sensors to include in the group", "group_power_entities": "Additional power sensors (W) from your HA installation to include", "group_energy_entities": "Additional energy sensors (kWh) from your HA installation to include", - "sub_groups": "All containing sensors from the selected subgroups will be added to this group as well" + "sub_groups": "All containing sensors from the selected subgroups will be added to this group as well", + "area": "Adds all powercalc sensors from the specified area" } }, "virtual_power": { @@ -191,6 +193,7 @@ "group_member_sensors": "[%key:component::powercalc::config::step::group::data::group_member_sensors%]", "group_power_entities": "[%key:component::powercalc::config::step::group::data::group_power_entities%]", "group_energy_entities": "[%key:component::powercalc::config::step::group::data::group_energy_entities%]", + "area": "[%key:component::powercalc::config::step::group::data::area%]", "sub_groups": "[%key:component::powercalc::config::step::group::data::sub_groups%]", "hide_members": "[%key:component::powercalc::config::step::group::data::hide_members%]", "energy_integration_method": "[%key:component::powercalc::config::step::power_advanced::data::energy_integration_method%]", @@ -204,6 +207,7 @@ "attribute": "[%key:component::powercalc::config::step::linear::data_description::attribute%]", "power_template": "[%key:component::powercalc::config::step::fixed::data_description::power_template%]", "states_power": "[%key:component::powercalc::config::step::fixed::data_description::states_power%]", + "area": "[%key:component::powercalc::config::step::group::data_description::area%]", "sub_groups": "[%key:component::powercalc::config::step::group::data_description::sub_groups%]", "group_member_sensors": "[%key:component::powercalc::config::step::group::data_description::group_member_sensors%]", "group_power_entities": "[%key:component::powercalc::config::step::group::data_description::group_power_entities%]", diff --git a/custom_components/powercalc/translations/en.json b/custom_components/powercalc/translations/en.json index 73ac17f8a..249e5043d 100644 --- a/custom_components/powercalc/translations/en.json +++ b/custom_components/powercalc/translations/en.json @@ -56,6 +56,7 @@ "group_energy_entities": "Additional energy entities", "name": "Name", "sub_groups": "Sub groups", + "area": "Area", "hide_members": "Hide members", "unique_id": "Unique id" }, @@ -63,7 +64,8 @@ "group_member_sensors": "Powercalc sensors to include in the group", "group_power_entities": "Additional power sensors (W) from your HA installation to include", "group_energy_entities": "Additional energy sensors (kWh) from your HA installation to include", - "sub_groups": "All containing sensors from the selected subgroups will be added to this group as well" + "sub_groups": "All containing sensors from the selected subgroups will be added to this group as well", + "area": "Adds all powercalc sensors from the specified area" }, "title": "Create a group sensor" }, @@ -179,6 +181,7 @@ "step": { "init": { "data": { + "area": "Area", "attribute": "Attribute", "calculation_enabled_condition": "Calculation enabled condition", "calibrate": "Calibration values", @@ -208,6 +211,7 @@ "value_template": "Value template" }, "data_description": { + "area": "Adds all powercalc sensors from the specified area", "attribute": "Specify the attribute. When left empty will be brightness for lights and percentage for fans", "calculation_enabled_condition": "The configured power calculation strategy will only be executed when this template evaluates to true or 1, otherwise the power sensor will display 0", "calibrate": "Put a calibration value on each line. Example\n\n1: 20", diff --git a/custom_components/powercalc/translations/nl.json b/custom_components/powercalc/translations/nl.json index ffb1448ba..71e0e5673 100644 --- a/custom_components/powercalc/translations/nl.json +++ b/custom_components/powercalc/translations/nl.json @@ -56,6 +56,7 @@ "group_power_entities": "Additionele vermogen entiteiten", "name": "Naam", "sub_groups": "Sub groepen", + "area": "Ruimte", "hide_members": "Verberg onderliggende sensoren", "unique_id": "Uniek id" }, @@ -63,7 +64,8 @@ "group_member_sensors": "Powercalc sensoren die je wil toevoegen aan de groep", "group_power_entities": "Additionele vermogen entiteiten (W)", "group_energy_entities": "Additionele energie entiteiten (kWh)", - "sub_groups": "Alle onderliggende sensoren van de geselecteerde sub-groepen worden ook automatisch aan deze groep toegevoegd." + "sub_groups": "Alle onderliggende sensoren van de geselecteerde sub-groepen worden ook automatisch aan deze groep toegevoegd.", + "area": "Voeg alle powercalc sensoren van de gespecificeerde ruimte toe" }, "title": "Creƫer een groep sensor" }, @@ -179,6 +181,7 @@ "step": { "init": { "data": { + "area": "Ruimte", "attribute": "Attribuut", "calculation_enabled_condition": "Calculatie ingeschakeld conditie", "calibrate": "Kalibratie waardes", @@ -208,6 +211,7 @@ "value_template": "Waarde template" }, "data_description": { + "area": "Voeg alle powercalc sensoren van de gespecificeerde ruimte toe", "attribute": "Specificeer een attribuut. Wanneer je dit leeg laat dan wordt helderheid gebruikt voor verlichting en percentage voor ventilatoren", "calculation_enabled_condition": "De geconfigureerde vermogen calculatie strategie wordt alleen uitgevoerd indien dit template evalueert naar 1, anders zal de energiesensor 0 tonen", "calibrate": "Kalibratiewaarde op iedere regel. Voorbeeld\n\n1: 20", diff --git a/tests/test_config_flow.py b/tests/test_config_flow.py index 495cadbca..3e39eac5f 100644 --- a/tests/test_config_flow.py +++ b/tests/test_config_flow.py @@ -7,6 +7,7 @@ from homeassistant import config_entries, data_entry_flow from homeassistant.components import sensor from homeassistant.const import ( + CONF_ENTITIES, CONF_ENTITY_ID, CONF_NAME, CONF_PLATFORM, @@ -17,6 +18,8 @@ ) from homeassistant.core import HomeAssistant from homeassistant.data_entry_flow import FlowResult +from homeassistant.helpers.area_registry import AreaRegistry +from homeassistant.helpers.entity_registry import EntityRegistry from homeassistant.helpers.selector import SelectSelector from homeassistant.helpers.typing import ConfigType from homeassistant.setup import async_setup_component @@ -29,6 +32,7 @@ MENU_OPTION_LIBRARY, ) from custom_components.powercalc.const import ( + CONF_AREA, CONF_CALCULATION_ENABLED_CONDITION, CONF_CREATE_ENERGY_SENSOR, CONF_CREATE_UTILITY_METERS, @@ -708,6 +712,54 @@ async def test_create_group_entry_without_unique_id(hass: HomeAssistant) -> None assert hass.states.get("sensor.my_group_sensor_power") +async def test_group_include_area(hass: HomeAssistant, entity_reg: EntityRegistry, + area_reg: AreaRegistry) -> None: + + # Create light entity and add to group My area + light = MockLight("test") + await create_mock_light_entity(hass, light) + area = area_reg.async_get_or_create("My area") + entity_reg.async_update_entity(light.entity_id, area_id=area.id) + + result = await _goto_virtual_power_strategy_step(hass, CalculationStrategy.FIXED, {CONF_ENTITY_ID: "light.test"}) + await _set_virtual_power_configuration( + hass, + result, + {CONF_STATES_POWER: {"playing": 1.8}}, + ) + + result = await _select_sensor_type(hass, SensorType.GROUP) + user_input = { + CONF_NAME: "My group sensor", + CONF_AREA: area.id, + CONF_CREATE_UTILITY_METERS: True, + } + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input, + ) + assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY + assert result["data"] == { + CONF_SENSOR_TYPE: SensorType.GROUP, + CONF_NAME: "My group sensor", + CONF_HIDE_MEMBERS: False, + CONF_AREA: area.id, + CONF_UNIQUE_ID: "My group sensor", + CONF_CREATE_UTILITY_METERS: True, + } + await hass.async_block_till_done() + + power_state = hass.states.get("sensor.my_group_sensor_power") + assert power_state + assert power_state.attributes.get(CONF_ENTITIES) == {"sensor.test_power"} + + energy_state = hass.states.get("sensor.my_group_sensor_energy") + assert energy_state + assert energy_state.attributes.get(CONF_ENTITIES) == {"sensor.test_energy"} + + assert hass.states.get("sensor.my_group_sensor_energy_daily") + + async def test_can_select_existing_powercalc_entry_as_group_member( hass: HomeAssistant, ) -> None: