Ignore negative derivative when the input is total_increasing (#119141)

* if the derivative is negative, ignore it

* add option to ignore the negatives or not

* add tests for a new ignore negative derivative

* add missing description when editing

* rename to ignore_negative_derivative
to increase clarity of which negative I mean
in case in the future we want a ignore_negative_value...

* use state_class=total_increasing to ignore the negative derivative

* remove ignore negative from the config

* add test for total_increasing_reset case

* add comments

* update test_total_increasing_reset with history tests
Also remove the last comment because the test is already clear
My existing comment there isn't unique to this unit test but applies to the entire component. The existing web documentation pointing to Wikipedia should suffice.

---------

Co-authored-by: Erik Montnemery <erik@montnemery.com>
This commit is contained in:
Antony Kurniawan 2024-09-16 15:49:15 +07:00 committed by GitHub
parent c77a3674b0
commit 156a88a3a3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 48 additions and 0 deletions

View File

@ -10,9 +10,11 @@ from typing import TYPE_CHECKING
import voluptuous as vol
from homeassistant.components.sensor import (
ATTR_STATE_CLASS,
PLATFORM_SCHEMA as SENSOR_PLATFORM_SCHEMA,
RestoreSensor,
SensorEntity,
SensorStateClass,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
@ -238,6 +240,16 @@ class DerivativeSensor(RestoreSensor, SensorEntity):
except AssertionError as err:
_LOGGER.error("Could not calculate derivative: %s", err)
# For total inreasing sensors, the value is expected to continuously increase.
# A negative derivative for a total increasing sensor likely indicates the
# sensor has been reset. To prevent inaccurate data, discard this sample.
if (
new_state.attributes.get(ATTR_STATE_CLASS)
== SensorStateClass.TOTAL_INCREASING
and new_derivative < 0
):
return
# add latest derivative to the window list
self._state_list.append(
(old_state.last_updated, new_state.last_updated, new_derivative)

View File

@ -8,6 +8,7 @@ from typing import Any
from freezegun import freeze_time
from homeassistant.components.derivative.const import DOMAIN
from homeassistant.components.sensor import ATTR_STATE_CLASS, SensorStateClass
from homeassistant.const import UnitOfPower, UnitOfTime
from homeassistant.core import HomeAssistant, State
from homeassistant.helpers import device_registry as dr, entity_registry as er
@ -354,6 +355,41 @@ async def test_suffix(hass: HomeAssistant) -> None:
assert round(float(state.state), config["sensor"]["round"]) == 0.0
async def test_total_increasing_reset(hass: HomeAssistant) -> None:
"""Test derivative sensor state with total_increasing sensor input where it should ignore the reset value."""
times = [0, 20, 30, 35, 40, 50, 60]
values = [0, 10, 30, 40, 0, 10, 40]
expected_times = [0, 20, 30, 35, 50, 60]
expected_values = ["0.00", "0.50", "2.00", "2.00", "1.00", "3.00"]
config, entity_id = await _setup_sensor(hass, {"unit_time": UnitOfTime.SECONDS})
base_time = dt_util.utcnow()
actual_times = []
actual_values = []
with freeze_time(base_time) as freezer:
for time, value in zip(times, values, strict=False):
current_time = base_time + timedelta(seconds=time)
freezer.move_to(current_time)
hass.states.async_set(
entity_id,
value,
{ATTR_STATE_CLASS: SensorStateClass.TOTAL_INCREASING},
force_update=True,
)
await hass.async_block_till_done()
state = hass.states.get("sensor.power")
assert state is not None
if state.last_reported == current_time:
actual_times.append(time)
actual_values.append(state.state)
assert actual_times == expected_times
assert actual_values == expected_values
async def test_device_id(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,