Skip to content

Commit

Permalink
fix issue where energy group calculation had minor errors because of …
Browse files Browse the repository at this point in the history
…rounding precision (#2259)

* fix: issue where energy group calculation had minor errors because of rounding precision

* fix: mypy
  • Loading branch information
bramstroker committed May 26, 2024
1 parent 711f92a commit 5469f6a
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 19 deletions.
32 changes: 16 additions & 16 deletions custom_components/powercalc/sensors/group.py
Original file line number Diff line number Diff line change
Expand Up @@ -516,6 +516,7 @@ def __init__(
self.entity_id = entity_id
self.source_device_id = device_id
self._prev_state_store: PreviousStateStore = PreviousStateStore(hass)
self._native_value_exact = Decimal(0)

async def async_added_to_hass(self) -> None:
"""Register state listeners."""
Expand All @@ -527,15 +528,9 @@ async def async_added_to_hass(self) -> None:
last_sensor_state = await self.async_get_last_sensor_data()
try:
if last_sensor_state and last_sensor_state.native_value:
self._attr_native_value = round(
Decimal(last_sensor_state.native_value), # type: ignore
self._rounding_digits,
)
self._set_native_value(Decimal(last_sensor_state.native_value)) #type: ignore
elif last_state:
self._attr_native_value = round(
Decimal(last_state.state),
self._rounding_digits,
)
self._set_native_value(Decimal(last_state.state))
_LOGGER.debug(
"%s: Restoring state: %s",
self.entity_id,
Expand Down Expand Up @@ -600,21 +595,21 @@ def on_state_change(self, _: Any) -> None: # noqa
if not available_states:
if self._sensor_config.get(CONF_IGNORE_UNAVAILABLE_STATE):
if isinstance(self, GroupedPowerSensor):
self._attr_native_value = 0
self._set_native_value(Decimal(0))
self._attr_available = True
else:
self._attr_available = False
self.async_write_ha_state()
return

summed = self.calculate_new_state(available_states, states)
self._attr_native_value = round(summed, self._rounding_digits)
self._set_native_value(summed)
self._attr_available = True
self.async_write_ha_state()

def _get_state_value_in_native_unit(self, state: State) -> Decimal:
"""Convert value of member entity state to match the unit of measurement of the group sensor."""
value = float(state.state)
value = state.state
unit_of_measurement = state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
if (
unit_of_measurement
Expand All @@ -626,12 +621,17 @@ def _get_state_value_in_native_unit(self, state: State) -> Decimal:
else PowerConverter
)
value = unit_converter.convert(
value,
float(value),
unit_of_measurement,
self._attr_native_unit_of_measurement,
)
return Decimal(value)

def _set_native_value(self, value: Decimal) -> None:
self._native_value_exact = value
self._attr_native_value = round(value, self._rounding_digits)
self.async_write_ha_state()

@abstractmethod
def calculate_new_state(
self,
Expand Down Expand Up @@ -698,7 +698,7 @@ def __init__(
async def async_reset(self) -> None:
"""Reset the group sensor and underlying member sensor when supported."""
_LOGGER.debug("%s: Reset grouped energy sensor", self.entity_id)
self._attr_native_value = 0
self._set_native_value(Decimal(0))
self.async_write_ha_state()

for entity_id in self._entities:
Expand All @@ -718,7 +718,7 @@ async def async_reset(self) -> None:

async def async_calibrate(self, value: str) -> None:
_LOGGER.debug("%s: Calibrate group energy sensor to: %s", self.entity_id, value)
self._attr_native_value = Decimal(value)
self._set_native_value(Decimal(value))
self.async_write_ha_state()

def calculate_new_state(
Expand All @@ -729,8 +729,8 @@ def calculate_new_state(
"""Calculate the new group energy sensor state
For each member sensor we calculate the delta by looking at the previous known state and compare it to the current.
"""
group_sum = Decimal(self._attr_native_value) if self._attr_native_value else Decimal(0) # type: ignore
_LOGGER.debug("%s: Recalculate, current value: %d", self.entity_id, group_sum)
group_sum = Decimal(self._native_value_exact) if self._native_value_exact else Decimal(0)
_LOGGER.debug("%s: Recalculate, current value: %s", self.entity_id, group_sum)
for entity_state in member_states:
if entity_state.state in [STATE_UNKNOWN, STATE_UNAVAILABLE]:
_LOGGER.debug(
Expand Down
36 changes: 33 additions & 3 deletions tests/sensors/test_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ async def test_reset_service(hass: HomeAssistant) -> None:
)
await hass.async_block_till_done()

assert hass.states.get("sensor.testgroup_energy").state == "0"
assert hass.states.get("sensor.testgroup_energy").state == "0.0000"
assert hass.states.get("sensor.test1_energy").state == "0"
assert hass.states.get("sensor.test2_energy").state == "0"

Expand Down Expand Up @@ -352,7 +352,7 @@ async def test_calibrate_service(hass: HomeAssistant) -> None:
)
await hass.async_block_till_done()

assert hass.states.get("sensor.testgroup_energy").state == "100"
assert hass.states.get("sensor.testgroup_energy").state == "100.0000"


async def test_restore_state(hass: HomeAssistant) -> None:
Expand Down Expand Up @@ -1128,6 +1128,36 @@ async def test_energy_sensor_delta_updates_new_sensor(hass: HomeAssistant) -> No
assert hass.states.get("sensor.testgroup_energy").state == "5.3000"


async def test_delta_calculation_precision(hass: HomeAssistant) -> None:
"""
Make sure delta calculation is done on exact decimal value, not the rounded value.
See: https://github.com/bramstroker/homeassistant-powercalc/issues/2254
"""
await _create_energy_group(
hass,
"TestGroup",
["sensor.a_energy"],
)

test_values = [
("1197.865543", "1197.8655"),
("1197.868021", "1197.8680"),
("1197.868628", "1197.8686"),
("1197.871022", "1197.8710"),
("1197.871629", "1197.8716"),
("1197.873903", "1197.8739"),
("1197.874396", "1197.8744"),
("1197.876372", "1197.8764"),
("1197.876868", "1197.8769"),
("1197.878879", "1197.8789"),
("1197.879332", "1197.8793"),
]

for energy_state, expected_group_state in test_values:
hass.states.async_set("sensor.a_energy", energy_state, {ATTR_UNIT_OF_MEASUREMENT: UnitOfEnergy.KILO_WATT_HOUR})
await hass.async_block_till_done()
assert hass.states.get("sensor.testgroup_energy").state == expected_group_state

async def test_energy_sensor_delta_updates_existing_sensor(hass: HomeAssistant) -> None:
await _create_energy_group(
hass,
Expand Down Expand Up @@ -1428,7 +1458,7 @@ async def test_inital_group_sum_calculated(hass: HomeAssistant) -> None:

group_state = hass.states.get("sensor.testgroup_power")
assert group_state
assert group_state.state == "0"
assert group_state.state == "0.00"


async def test_additional_energy_sensors(hass: HomeAssistant) -> None:
Expand Down

0 comments on commit 5469f6a

Please sign in to comment.