Skip to content

Commit 42839b9

Browse files
authored
fix: also remove member sensor from powercalc_groups storage when removed from group (#3972)
1 parent bd0a4f6 commit 42839b9

File tree

2 files changed

+92
-8
lines changed

2 files changed

+92
-8
lines changed

custom_components/powercalc/sensors/group/custom.py

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -456,7 +456,6 @@ def __init__(
456456
self._attr_suggested_display_precision = self._rounding_digits
457457
if unique_id:
458458
self._attr_unique_id = unique_id
459-
self._prev_state_store: PreviousStateStore = PreviousStateStore(hass)
460459
self._native_value_exact = Decimal(0)
461460
self._member_states: dict[str, Decimal] = {}
462461
self._ignore_unavailable_state = bool(self._sensor_config.get(CONF_IGNORE_UNAVAILABLE_STATE))
@@ -470,7 +469,6 @@ async def async_added_to_hass(self) -> None:
470469
"""Register state listeners."""
471470
await super().async_added_to_hass()
472471

473-
self._prev_state_store = await PreviousStateStore.async_get_instance(self.hass)
474472
if self._update_interval > 0:
475473
self.async_on_remove(self._cancel_update_interval_exceeded_callback)
476474

@@ -479,6 +477,12 @@ async def async_added_to_hass(self) -> None:
479477
if CONF_HIDE_MEMBERS in self._sensor_config:
480478
self._async_hide_members(bool(self._sensor_config.get(CONF_HIDE_MEMBERS)))
481479

480+
if not self._sensor_config.get(CONF_DISABLE_EXTENDED_ATTRIBUTES, False):
481+
self._attr_extra_state_attributes = {
482+
ATTR_ENTITIES: self._entities,
483+
ATTR_IS_GROUP: True,
484+
}
485+
482486
async def async_will_remove_from_hass(self) -> None:
483487
"""
484488
This will trigger when entity is about to be removed from HA
@@ -547,12 +551,6 @@ async def on_start(self, _: Any) -> None: # noqa
547551
),
548552
)
549553

550-
if not self._sensor_config.get(CONF_DISABLE_EXTENDED_ATTRIBUTES, False):
551-
self._attr_extra_state_attributes = {
552-
ATTR_ENTITIES: self._entities,
553-
ATTR_IS_GROUP: True,
554-
}
555-
556554
await self.initial_update()
557555

558556
async def initial_update(self) -> None:
@@ -720,9 +718,15 @@ def __init__(
720718
sensor_config.get(CONF_ENERGY_SENSOR_UNIT_PREFIX, UnitPrefix.NONE),
721719
UnitOfEnergy.WATT_HOUR,
722720
)
721+
self._prev_state_store: PreviousStateStore = PreviousStateStore(hass)
723722

724723
async def async_added_to_hass(self) -> None:
725724
"""Register state listeners."""
725+
726+
self._prev_state_store = await PreviousStateStore.async_get_instance(self.hass)
727+
# Clean up any entities that are no longer part of the group
728+
self._prev_state_store.cleanup_entity_states(self.entity_id, self._entities)
729+
726730
await self.restore_last_state()
727731

728732
await super().async_added_to_hass()
@@ -900,6 +904,20 @@ def set_entity_state(self, group: str, entity_id: str, state: State) -> None:
900904
"""Set the state for an energy sensor."""
901905
self.states.setdefault(group, {})[entity_id] = state
902906

907+
def cleanup_entity_states(self, group: str, current_entities: set[str]) -> None:
908+
"""Remove entity states that are no longer part of the group."""
909+
group_states = self.states.get(group)
910+
if group_states is None:
911+
return
912+
913+
# Find entities that are in the store but not in the current set
914+
entities_to_remove = set(group_states.keys()) - current_entities
915+
916+
# Remove those entities from the store
917+
for entity_id in entities_to_remove:
918+
_LOGGER.debug("Removing entity %s from group %s in PreviousStateStore", entity_id, group)
919+
group_states.pop(entity_id, None)
920+
903921
async def persist_states(self) -> None:
904922
"""Save the current states to storage."""
905923
try:

tests/sensors/group/test_custom.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1992,6 +1992,72 @@ async def test_resolve_entity_ids_skips_tasmota_yesterday_and_today(hass: HomeAs
19921992
assert resolved == {"sensor.test_total"}
19931993

19941994

1995+
async def test_remove_member_from_group(hass: HomeAssistant) -> None:
1996+
state_storage: PreviousStateStore = await PreviousStateStore.async_get_instance(hass)
1997+
1998+
# Setup 3 powercalc virtual power sensor and one group sensor
1999+
member_config_entries = []
2000+
member_entity_ids = []
2001+
for i in range(3):
2002+
config_entry = await setup_config_entry(
2003+
hass,
2004+
{
2005+
CONF_SENSOR_TYPE: SensorType.VIRTUAL_POWER,
2006+
CONF_UNIQUE_ID: f"pc{i}",
2007+
CONF_ENTITY_ID: DUMMY_ENTITY_ID,
2008+
CONF_NAME: f"VirtualSensor{i}",
2009+
CONF_MODE: CalculationStrategy.FIXED,
2010+
CONF_FIXED: {CONF_POWER: 50},
2011+
},
2012+
)
2013+
member_config_entries.append(config_entry)
2014+
member_entity_ids.append(f"sensor.virtualsensor{i}_energy")
2015+
2016+
group_entry = await setup_config_entry(
2017+
hass,
2018+
{
2019+
CONF_SENSOR_TYPE: SensorType.GROUP,
2020+
CONF_NAME: "TestGroup",
2021+
CONF_GROUP_MEMBER_SENSORS: [entry.entry_id for entry in member_config_entries],
2022+
},
2023+
)
2024+
2025+
# Trigger some changes on member power sensor so the group energy sensor integration logic is executed.
2026+
for i in range(3):
2027+
hass.states.async_set(f"sensor.virtualsensor{i}_power", "60.00")
2028+
await hass.async_block_till_done()
2029+
await hass.async_block_till_done()
2030+
2031+
# Assert the group sensor has the correct 3 member sensors added
2032+
group_state = hass.states.get("sensor.testgroup_energy")
2033+
assert group_state
2034+
assert group_state.attributes.get(CONF_ENTITIES) == set(member_entity_ids)
2035+
2036+
assert state_storage.get_entity_state("sensor.testgroup_energy", member_entity_ids[0])
2037+
assert state_storage.get_entity_state("sensor.testgroup_energy", member_entity_ids[1])
2038+
assert state_storage.get_entity_state("sensor.testgroup_energy", member_entity_ids[2])
2039+
2040+
# Remove one powercalc sensor from the group
2041+
member_config_entries.remove(member_config_entries[1])
2042+
member_entity_ids.remove(member_entity_ids[1])
2043+
hass.config_entries.async_update_entry(
2044+
group_entry,
2045+
data={
2046+
**group_entry.data,
2047+
CONF_GROUP_MEMBER_SENSORS: [entry.entry_id for entry in member_config_entries],
2048+
},
2049+
)
2050+
2051+
# Assert the group sensor has the correct 2 member sensors added, and the removed sensor is not there anymore
2052+
group_state = hass.states.get("sensor.testgroup_energy")
2053+
assert group_state
2054+
assert group_state.attributes.get(CONF_ENTITIES) == set(member_entity_ids)
2055+
2056+
assert state_storage.get_entity_state("sensor.testgroup_energy", member_entity_ids[0])
2057+
assert not state_storage.get_entity_state("sensor.testgroup_energy", "sensor.virtualsensor1_energy")
2058+
assert state_storage.get_entity_state("sensor.testgroup_energy", member_entity_ids[1])
2059+
2060+
19952061
async def _create_energy_group(
19962062
hass: HomeAssistant,
19972063
name: str,

0 commit comments

Comments
 (0)