Skip to content

Commit

Permalink
Dont count unrecorded time for history_stats (#126271)
Browse files Browse the repository at this point in the history
  • Loading branch information
karwosts authored Nov 21, 2024
1 parent 23acc31 commit 3d499ab
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 45 deletions.
15 changes: 8 additions & 7 deletions homeassistant/components/history_stats/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,26 +176,27 @@ def _async_compute_seconds_and_changes(
# state_changes_during_period is called with include_start_time_state=True
# which is the default and always provides the state at the start
# of the period
previous_state_matches = (
self._history_current_period
and self._history_current_period[0].state in self._entity_states
)
last_state_change_timestamp = start_timestamp
previous_state_matches = False
last_state_change_timestamp = 0.0
elapsed = 0.0
match_count = 1 if previous_state_matches else 0
match_count = 0

# Make calculations
for history_state in self._history_current_period:
current_state_matches = history_state.state in self._entity_states
state_change_timestamp = history_state.last_changed

if state_change_timestamp > now_timestamp:
# Shouldn't count states that are in the future
continue

if previous_state_matches:
elapsed += state_change_timestamp - last_state_change_timestamp
elif current_state_matches:
match_count += 1

previous_state_matches = current_state_matches
last_state_change_timestamp = state_change_timestamp
last_state_change_timestamp = max(start_timestamp, state_change_timestamp)

# Count time elapsed between last history state and end of measure
if previous_state_matches:
Expand Down
79 changes: 41 additions & 38 deletions tests/components/history_stats/test_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -437,10 +437,10 @@ def _fake_states(*args, **kwargs):
await async_update_entity(hass, f"sensor.sensor{i}")
await hass.async_block_till_done()

assert hass.states.get("sensor.sensor1").state == "0.83"
assert hass.states.get("sensor.sensor2").state == "0.833333333333333"
assert hass.states.get("sensor.sensor1").state == "0.5"
assert 0.499 < float(hass.states.get("sensor.sensor2").state) < 0.501
assert hass.states.get("sensor.sensor3").state == "2"
assert hass.states.get("sensor.sensor4").state == "83.3"
assert hass.states.get("sensor.sensor4").state == "50.0"


async def test_async_on_entire_period(
Expand Down Expand Up @@ -1254,10 +1254,10 @@ def _fake_states(*args, **kwargs):
await async_update_entity(hass, f"sensor.sensor{i}")
await hass.async_block_till_done()

assert hass.states.get("sensor.sensor1").state == "0.83"
assert hass.states.get("sensor.sensor2").state == "0.833333333333333"
assert hass.states.get("sensor.sensor3").state == "2"
assert hass.states.get("sensor.sensor4").state == "41.7"
assert hass.states.get("sensor.sensor1").state == "0.0"
assert float(hass.states.get("sensor.sensor2").state) == 0
assert hass.states.get("sensor.sensor3").state == "0"
assert hass.states.get("sensor.sensor4").state == "0.0"

past_next_update = start_time + timedelta(minutes=30)
with (
Expand All @@ -1268,12 +1268,12 @@ def _fake_states(*args, **kwargs):
freeze_time(past_next_update),
):
async_fire_time_changed(hass, past_next_update)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)

assert hass.states.get("sensor.sensor1").state == "0.83"
assert hass.states.get("sensor.sensor2").state == "0.833333333333333"
assert hass.states.get("sensor.sensor3").state == "2"
assert hass.states.get("sensor.sensor4").state == "41.7"
assert hass.states.get("sensor.sensor1").state == "0.17"
assert 0.166 < float(hass.states.get("sensor.sensor2").state) < 0.167
assert hass.states.get("sensor.sensor3").state == "1"
assert hass.states.get("sensor.sensor4").state == "8.3"


async def test_measure_from_end_going_backwards(
Expand Down Expand Up @@ -1355,10 +1355,10 @@ def _fake_states(*args, **kwargs):
await async_update_entity(hass, f"sensor.sensor{i}")
await hass.async_block_till_done()

assert hass.states.get("sensor.sensor1").state == "0.83"
assert hass.states.get("sensor.sensor2").state == "0.833333333333333"
assert hass.states.get("sensor.sensor3").state == "2"
assert hass.states.get("sensor.sensor4").state == "83.3"
assert hass.states.get("sensor.sensor1").state == "0.0"
assert float(hass.states.get("sensor.sensor2").state) == 0
assert hass.states.get("sensor.sensor3").state == "0"
assert hass.states.get("sensor.sensor4").state == "0.0"

past_next_update = start_time + timedelta(minutes=30)
with (
Expand All @@ -1369,12 +1369,12 @@ def _fake_states(*args, **kwargs):
freeze_time(past_next_update),
):
async_fire_time_changed(hass, past_next_update)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)

assert hass.states.get("sensor.sensor1").state == "0.83"
assert hass.states.get("sensor.sensor2").state == "0.833333333333333"
assert hass.states.get("sensor.sensor3").state == "2"
assert hass.states.get("sensor.sensor4").state == "83.3"
assert hass.states.get("sensor.sensor1").state == "0.17"
assert 0.166 < float(hass.states.get("sensor.sensor2").state) < 0.167
assert hass.states.get("sensor.sensor3").state == "1"
assert 16.6 <= float(hass.states.get("sensor.sensor4").state) <= 16.7


async def test_measure_cet(recorder_mock: Recorder, hass: HomeAssistant) -> None:
Expand Down Expand Up @@ -1403,7 +1403,7 @@ def _fake_states(*args, **kwargs):
"homeassistant.components.recorder.history.state_changes_during_period",
_fake_states,
),
freeze_time(start_time),
freeze_time(start_time + timedelta(minutes=60)),
):
await async_setup_component(
hass,
Expand Down Expand Up @@ -1455,10 +1455,10 @@ def _fake_states(*args, **kwargs):
await async_update_entity(hass, f"sensor.sensor{i}")
await hass.async_block_till_done()

assert hass.states.get("sensor.sensor1").state == "0.83"
assert hass.states.get("sensor.sensor2").state == "0.833333333333333"
assert hass.states.get("sensor.sensor1").state == "0.5"
assert 0.499 < float(hass.states.get("sensor.sensor2").state) < 0.501
assert hass.states.get("sensor.sensor3").state == "2"
assert hass.states.get("sensor.sensor4").state == "83.3"
assert hass.states.get("sensor.sensor4").state == "50.0"


@pytest.mark.parametrize("time_zone", ["Europe/Berlin", "America/Chicago", "US/Hawaii"])
Expand Down Expand Up @@ -1537,18 +1537,19 @@ def _fake_states(*args, **kwargs):
await hass.async_block_till_done()
await async_update_entity(hass, "sensor.heatpump_compressor_today")
await hass.async_block_till_done()
assert hass.states.get("sensor.heatpump_compressor_today").state == "1.83"
assert hass.states.get("sensor.heatpump_compressor_today").state == "0.5"
assert (
hass.states.get("sensor.heatpump_compressor_today2").state
== "1.83333333333333"
0.499
< float(hass.states.get("sensor.heatpump_compressor_today2").state)
< 0.501
)

async_fire_time_changed(hass, time_200)
await hass.async_block_till_done(wait_background_tasks=True)
assert hass.states.get("sensor.heatpump_compressor_today").state == "1.83"
assert hass.states.get("sensor.heatpump_compressor_today").state == "0.5"
assert (
hass.states.get("sensor.heatpump_compressor_today2").state
== "1.83333333333333"
0.499
< float(hass.states.get("sensor.heatpump_compressor_today2").state)
< 0.501
)
hass.states.async_set("binary_sensor.heatpump_compressor_state", "off")
await hass.async_block_till_done()
Expand All @@ -1557,21 +1558,23 @@ def _fake_states(*args, **kwargs):
with freeze_time(time_400):
async_fire_time_changed(hass, time_400)
await hass.async_block_till_done(wait_background_tasks=True)
assert hass.states.get("sensor.heatpump_compressor_today").state == "1.83"
assert hass.states.get("sensor.heatpump_compressor_today").state == "0.5"
assert (
hass.states.get("sensor.heatpump_compressor_today2").state
== "1.83333333333333"
0.499
< float(hass.states.get("sensor.heatpump_compressor_today2").state)
< 0.501
)
hass.states.async_set("binary_sensor.heatpump_compressor_state", "on")
await async_wait_recording_done(hass)
time_600 = start_of_today + timedelta(hours=6)
with freeze_time(time_600):
async_fire_time_changed(hass, time_600)
await hass.async_block_till_done(wait_background_tasks=True)
assert hass.states.get("sensor.heatpump_compressor_today").state == "3.83"
assert hass.states.get("sensor.heatpump_compressor_today").state == "2.5"
assert (
hass.states.get("sensor.heatpump_compressor_today2").state
== "3.83333333333333"
2.499
< float(hass.states.get("sensor.heatpump_compressor_today2").state)
< 2.501
)

rolled_to_next_day = start_of_today + timedelta(days=1)
Expand Down

0 comments on commit 3d499ab

Please sign in to comment.