Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Option in config flow to add virtual power sensor to group on creation #992

Merged
merged 6 commits into from
Aug 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion custom_components/powercalc/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@
get_power_profile,
has_manufacturer_and_model_information,
)
from .sensors.group import create_group_sensors
from .sensors.group import create_group_sensors, update_associated_group_entry
from .strategy.factory import PowerCalculatorStrategyFactory

PLATFORMS = [Platform.SENSOR]
Expand Down Expand Up @@ -220,6 +220,10 @@ async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) ->
)

if unload_ok:
updated_group_entry = await update_associated_group_entry(hass, config_entry, remove=True)
if updated_group_entry:
await hass.config_entries.async_reload(updated_group_entry.entry_id)

used_unique_ids: list[str] = hass.data[DOMAIN][DATA_USED_UNIQUE_IDS]
try:
used_unique_ids.remove(config_entry.unique_id)
Expand Down
52 changes: 30 additions & 22 deletions custom_components/powercalc/config_flow.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Config flow for Adaptive Lighting integration."""

from __future__ import annotations
from audioop import mul

import copy
import logging
Expand All @@ -21,7 +22,7 @@
Platform,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.data_entry_flow import FlowResult
from homeassistant.data_entry_flow import FlowResult, FlowHandler
from homeassistant.helpers import selector

from .common import SourceEntity, create_source_entity
Expand All @@ -32,6 +33,7 @@
CONF_DAILY_FIXED_ENERGY,
CONF_FIXED,
CONF_GAMMA_CURVE,
CONF_GROUP,
CONF_GROUP_ENERGY_ENTITIES,
CONF_GROUP_MEMBER_SENSORS,
CONF_GROUP_POWER_ENTITIES,
Expand Down Expand Up @@ -116,7 +118,7 @@
}
)

SCHEMA_POWER = vol.Schema(
SCHEMA_POWER_BASE = vol.Schema(
{
vol.Required(CONF_ENTITY_ID): selector.EntitySelector(),
vol.Optional(CONF_NAME): selector.TextSelector(),
Expand All @@ -135,7 +137,7 @@
)
),
}
).extend(SCHEMA_POWER_OPTIONS.schema)
)

SCHEMA_POWER_FIXED = vol.Schema(
{
Expand Down Expand Up @@ -225,7 +227,7 @@ async def async_step_virtual_power(

return self.async_show_form(
step_id="virtual_power",
data_schema=SCHEMA_POWER,
data_schema=_create_virtual_power_schema(self.hass),
errors={},
)

Expand Down Expand Up @@ -440,7 +442,6 @@ def create_config_entry(self) -> FlowResult:
self.sensor_config.update({CONF_UNIQUE_ID: self.unique_id})
return self.async_create_entry(title=self.name, data=self.sensor_config)


class OptionsFlowHandler(OptionsFlow):
"""Handle an option flow for PowerCalc."""

Expand Down Expand Up @@ -525,10 +526,9 @@ def build_options_schema(self) -> vol.Schema:

strategy_options = {}
if self.sensor_type == SensorType.VIRTUAL_POWER:
base_power_schema = SCHEMA_POWER_OPTIONS
strategy: str = self.current_config.get(CONF_MODE)
strategy_schema = _get_strategy_schema(strategy, self.source_entity_id)
data_schema = base_power_schema.extend(strategy_schema.schema)
data_schema = SCHEMA_POWER_OPTIONS.extend(strategy_schema.schema)
strategy_options = self.current_config.get(strategy) or {}

if self.sensor_type == SensorType.DAILY_ENERGY:
Expand Down Expand Up @@ -568,23 +568,16 @@ def _get_strategy_schema(strategy: str, source_entity_id: str) -> vol.Schema:
if strategy == CalculationStrategy.LUT:
return vol.Schema({})

def _create_virtual_power_schema(hass: HomeAssistant) -> vol.Schema:
base_schema: vol.Schema = SCHEMA_POWER_BASE.extend(
{
vol.Optional(CONF_GROUP): _create_group_selector(hass)
}
)
return base_schema.extend(SCHEMA_POWER_OPTIONS.schema)

def _create_group_options_schema(hass: HomeAssistant) -> vol.Schema:
"""Create config schema for groups"""
sub_groups = [
selector.SelectOptionDict(
value=config_entry.entry_id, label=config_entry.data.get(CONF_NAME)
)
for config_entry in hass.config_entries.async_entries(DOMAIN)
if config_entry.data.get(CONF_SENSOR_TYPE) == SensorType.GROUP
]

sub_group_selector = selector.SelectSelector(
selector.SelectSelectorConfig(
options=sub_groups, multiple=True, mode=selector.SelectSelectorMode.DROPDOWN
)
)

member_sensors = [
selector.SelectOptionDict(
value=config_entry.entry_id, label=config_entry.data.get(CONF_NAME)
Expand Down Expand Up @@ -618,14 +611,29 @@ def _create_group_options_schema(hass: HomeAssistant) -> vol.Schema:
multiple=True,
)
),
vol.Optional(CONF_SUB_GROUPS): sub_group_selector,
vol.Optional(CONF_SUB_GROUPS): _create_group_selector(hass, multiple=True),
vol.Optional(
CONF_CREATE_UTILITY_METERS, default=False
): selector.BooleanSelector(),
vol.Optional(CONF_HIDE_MEMBERS, default=False): selector.BooleanSelector(),
}
)

def _create_group_selector(hass: HomeAssistant, multiple: bool = False) -> selector.SelectSelector:
options = [
selector.SelectOptionDict(
value=config_entry.entry_id, label=config_entry.data.get(CONF_NAME)
)
for config_entry in hass.config_entries.async_entries(DOMAIN)
if config_entry.data.get(CONF_SENSOR_TYPE) == SensorType.GROUP
]

return selector.SelectSelector(
selector.SelectSelectorConfig(
options=options, multiple=multiple, mode=selector.SelectSelectorMode.DROPDOWN
)
)


def _validate_group_input(user_input: dict[str, str] = None) -> dict:
"""Validate the group form"""
Expand Down
8 changes: 7 additions & 1 deletion custom_components/powercalc/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@
CONF_ENERGY_SENSOR_UNIT_PREFIX,
CONF_FIXED,
CONF_GROUP,
CONF_GROUP_MEMBER_SENSORS,
CONF_HIDE_MEMBERS,
CONF_IGNORE_UNAVAILABLE_STATE,
CONF_INCLUDE,
Expand Down Expand Up @@ -130,7 +131,7 @@
create_daily_fixed_energy_sensor,
)
from .sensors.energy import create_energy_sensor
from .sensors.group import create_group_sensors, create_group_sensors_from_config_entry
from .sensors.group import create_group_sensors, create_group_sensors_from_config_entry, update_associated_group_entry
from .sensors.power import RealPowerSensor, VirtualPowerSensor, create_power_sensor
from .sensors.utility_meter import create_utility_meters
from .strategy.fixed import CONFIG_SCHEMA as FIXED_SCHEMA
Expand Down Expand Up @@ -263,8 +264,13 @@ async def async_setup_entry(
)
async_add_entities(entities)
return

# Add entry to an existing group
updated_group_entry = await update_associated_group_entry(hass, entry, remove=False)

await _async_setup_entities(hass, sensor_config, async_add_entities)
if updated_group_entry:
await hass.config_entries.async_reload(updated_group_entry.entry_id)


async def _async_setup_entities(
Expand Down
32 changes: 31 additions & 1 deletion custom_components/powercalc/sensors/group.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,17 @@
ATTR_IS_GROUP,
CONF_ENERGY_SENSOR_PRECISION,
CONF_ENERGY_SENSOR_UNIT_PREFIX,
CONF_GROUP,
CONF_GROUP_ENERGY_ENTITIES,
CONF_GROUP_MEMBER_SENSORS,
CONF_GROUP_POWER_ENTITIES,
CONF_HIDE_MEMBERS,
CONF_POWER_SENSOR_PRECISION,
CONF_SENSOR_TYPE,
CONF_SUB_GROUPS,
DOMAIN,
SERVICE_RESET_ENERGY,
SensorType,
UnitPrefix,
)
from .abstract import (
Expand Down Expand Up @@ -142,6 +145,33 @@ async def create_group_sensors_from_config_entry(

return group_sensors

async def update_associated_group_entry(hass: HomeAssistant, config_entry: ConfigEntry, remove: bool) -> ConfigEntry | None:
"""
Update the group config entry when the virtual power config entry is associated to a group
Adds the sensor to the group on creation of the config entry
Removes the sensor from the group on removal of the config entry
"""
sensor_type = config_entry.data.get(CONF_SENSOR_TYPE)
if sensor_type != SensorType.VIRTUAL_POWER:
return None
if CONF_GROUP not in config_entry.data:
return None

group_entry_id = config_entry.data.get(CONF_GROUP)
group_entry = hass.config_entries.async_get_entry(group_entry_id)
member_sensors = group_entry.data.get(CONF_GROUP_MEMBER_SENSORS) or []

if remove and config_entry.entry_id in member_sensors:
member_sensors.remove(config_entry.entry_id)
elif config_entry.entry_id not in member_sensors:
member_sensors.append(config_entry.entry_id)

hass.config_entries.async_update_entry(
group_entry,
data={**group_entry.data, CONF_GROUP_MEMBER_SENSORS: member_sensors}
)
return group_entry


@callback
def resolve_entity_ids_recursively(
Expand All @@ -159,7 +189,7 @@ def resolve_entity_ids_recursively(

# Include the power/energy sensors for an existing Virtual Power config entry
entity_reg = er.async_get(hass)
member_entry_ids = entry.data.get(CONF_GROUP_MEMBER_SENSORS)
member_entry_ids = entry.data.get(CONF_GROUP_MEMBER_SENSORS) or []
# Unfortunately device_class is not correctly set at this time in the entity_registry
# So we need to match on state_class.
state_class = (
Expand Down
1 change: 1 addition & 0 deletions custom_components/powercalc/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
"mode": "Calculation strategy",
"entity_id": "Source entity",
"unique_id": "Unique id",
"group": "Add to group",
"standby_power": "Standby power",
"create_energy_sensor": "Create energy sensor",
"create_utility_meters": "Create utility meters"
Expand Down
1 change: 1 addition & 0 deletions custom_components/powercalc/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@
"create_energy_sensor": "Create energy sensor",
"create_utility_meters": "Create utility meters",
"entity_id": "Source entity",
"group": "Add to group",
"mode": "Calculation strategy",
"name": "Name",
"standby_power": "Standby power",
Expand Down
1 change: 1 addition & 0 deletions custom_components/powercalc/translations/nl.json
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@
"create_energy_sensor": "Creëer energie sensoren",
"create_utility_meters": "Creëer utiliteit meters",
"entity_id": "Bron entiteit",
"group": "Aan groep toevoegen",
"mode": "Calculatie strategie",
"name": "Naam",
"standby_power": "Standby stroom",
Expand Down
68 changes: 68 additions & 0 deletions tests/sensors/test_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
CONF_CREATE_UTILITY_METERS,
CONF_ENERGY_SENSOR_UNIT_PREFIX,
CONF_FIXED,
CONF_GROUP,
CONF_GROUP_ENERGY_ENTITIES,
CONF_GROUP_MEMBER_SENSORS,
CONF_GROUP_POWER_ENTITIES,
Expand All @@ -56,6 +57,7 @@
from ..common import (
create_input_boolean,
create_input_booleans,
create_mocked_virtual_power_sensor_entry,
get_simple_fixed_config,
run_powercalc_setup_yaml_config,
)
Expand Down Expand Up @@ -500,3 +502,69 @@ async def test_include_config_entries_in_group(hass: HomeAssistant):
assert group_energy_state.attributes.get(ATTR_ENTITIES) == {
"sensor.virtualsensor_energy"
}

async def test_add_virtual_power_sensor_to_group_on_creation(hass: HomeAssistant):
"""
When creating a virtual power sensor using the config flow you can define a group you want to add it to
Test that the new sensors are added to the existing group correctly
"""

config_entry_sensor1 = await create_mocked_virtual_power_sensor_entry(hass, "VirtualSensor1", "xyz")

config_entry_group = MockConfigEntry(
domain=DOMAIN,
data={
CONF_SENSOR_TYPE: SensorType.GROUP,
CONF_NAME: "GroupA",
CONF_GROUP_MEMBER_SENSORS: [config_entry_sensor1.entry_id]
},
)
config_entry_group.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry_group.entry_id)
await hass.async_block_till_done()

config_entry_sensor2 = MockConfigEntry(
domain=DOMAIN,
data={
CONF_SENSOR_TYPE: SensorType.VIRTUAL_POWER,
CONF_NAME: "VirtualSensor2",
CONF_ENTITY_ID: DUMMY_ENTITY_ID,
CONF_UNIQUE_ID: "abc",
CONF_GROUP: config_entry_group.entry_id,
CONF_FIXED: {
CONF_POWER: 50
}
},
)
config_entry_sensor2.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry_sensor2.entry_id)
await hass.async_block_till_done()

config_entry_group = hass.config_entries.async_get_entry(config_entry_group.entry_id)
assert config_entry_group.data.get(CONF_GROUP_MEMBER_SENSORS) == [
config_entry_sensor1.entry_id,
config_entry_sensor2.entry_id
]

group_state = hass.states.get("sensor.groupa_power")
assert group_state
assert group_state.attributes.get("entities") == {
"sensor.virtualsensor1_power",
"sensor.virtualsensor2_power"
}

# Remove config entry from Home Assistant, and see if group is updated accordingly
await hass.config_entries.async_remove(config_entry_sensor2.entry_id)
await hass.async_block_till_done()

config_entry_group = hass.config_entries.async_get_entry(config_entry_group.entry_id)
assert config_entry_group.data.get(CONF_GROUP_MEMBER_SENSORS) == [
config_entry_sensor1.entry_id,
]

group_state = hass.states.get("sensor.groupa_power")
assert group_state
assert group_state.attributes.get("entities") == {
"sensor.virtualsensor1_power",
}