mirror of
https://github.com/home-assistant/core.git
synced 2025-05-05 14:39:16 +00:00
Fix a history stats bug when window and tracked state change simultaneously (#133770)
This commit is contained in:
parent
7ce563b0b4
commit
bb371c87d5
@ -118,9 +118,7 @@ class HistoryStats:
|
||||
<= current_period_end_timestamp
|
||||
):
|
||||
self._history_current_period.append(
|
||||
HistoryState(
|
||||
new_state.state, new_state.last_changed.timestamp()
|
||||
)
|
||||
HistoryState(new_state.state, new_state.last_changed_timestamp)
|
||||
)
|
||||
new_data = True
|
||||
if not new_data and current_period_end_timestamp < now_timestamp:
|
||||
@ -131,6 +129,16 @@ class HistoryStats:
|
||||
await self._async_history_from_db(
|
||||
current_period_start_timestamp, current_period_end_timestamp
|
||||
)
|
||||
if event and (new_state := event.data["new_state"]) is not None:
|
||||
if (
|
||||
current_period_start_timestamp
|
||||
<= floored_timestamp(new_state.last_changed)
|
||||
<= current_period_end_timestamp
|
||||
):
|
||||
self._history_current_period.append(
|
||||
HistoryState(new_state.state, new_state.last_changed_timestamp)
|
||||
)
|
||||
|
||||
self._previous_run_before_start = False
|
||||
|
||||
seconds_matched, match_count = self._async_compute_seconds_and_changes(
|
||||
|
@ -1465,6 +1465,105 @@ async def test_measure_cet(recorder_mock: Recorder, hass: HomeAssistant) -> None
|
||||
assert hass.states.get("sensor.sensor4").state == "50.0"
|
||||
|
||||
|
||||
async def test_state_change_during_window_rollover(
|
||||
recorder_mock: Recorder,
|
||||
hass: HomeAssistant,
|
||||
) -> None:
|
||||
"""Test when the tracked sensor and the start/end window change during the same update."""
|
||||
await hass.config.async_set_time_zone("UTC")
|
||||
utcnow = dt_util.utcnow()
|
||||
start_time = utcnow.replace(hour=23, minute=0, second=0, microsecond=0)
|
||||
|
||||
def _fake_states(*args, **kwargs):
|
||||
return {
|
||||
"binary_sensor.state": [
|
||||
ha.State(
|
||||
"binary_sensor.state",
|
||||
"on",
|
||||
last_changed=start_time - timedelta(hours=11),
|
||||
last_updated=start_time - timedelta(hours=11),
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
# The test begins at 23:00, and queries from the database that the sensor has been on since 12:00.
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.recorder.history.state_changes_during_period",
|
||||
_fake_states,
|
||||
),
|
||||
freeze_time(start_time),
|
||||
):
|
||||
await async_setup_component(
|
||||
hass,
|
||||
"sensor",
|
||||
{
|
||||
"sensor": [
|
||||
{
|
||||
"platform": "history_stats",
|
||||
"entity_id": "binary_sensor.state",
|
||||
"name": "sensor1",
|
||||
"state": "on",
|
||||
"start": "{{ today_at() }}",
|
||||
"end": "{{ now() }}",
|
||||
"type": "time",
|
||||
}
|
||||
]
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await async_update_entity(hass, "sensor.sensor1")
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert hass.states.get("sensor.sensor1").state == "11.0"
|
||||
|
||||
# Advance 59 minutes, to record the last minute update just before midnight, just like a real system would do.
|
||||
t2 = start_time + timedelta(minutes=59, microseconds=300)
|
||||
with freeze_time(t2):
|
||||
async_fire_time_changed(hass, t2)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert hass.states.get("sensor.sensor1").state == "11.98"
|
||||
|
||||
# One minute has passed and the time has now rolled over into a new day, resetting the recorder window. The sensor will then query the database for updates,
|
||||
# and will see that the sensor is ON starting from midnight.
|
||||
t3 = t2 + timedelta(minutes=1)
|
||||
|
||||
def _fake_states_t3(*args, **kwargs):
|
||||
return {
|
||||
"binary_sensor.state": [
|
||||
ha.State(
|
||||
"binary_sensor.state",
|
||||
"on",
|
||||
last_changed=t3.replace(hour=0, minute=0, second=0, microsecond=0),
|
||||
last_updated=t3.replace(hour=0, minute=0, second=0, microsecond=0),
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.recorder.history.state_changes_during_period",
|
||||
_fake_states_t3,
|
||||
),
|
||||
freeze_time(t3),
|
||||
):
|
||||
# The sensor turns off around this time, before the sensor does its normal polled update.
|
||||
hass.states.async_set("binary_sensor.state", "off")
|
||||
await hass.async_block_till_done(wait_background_tasks=True)
|
||||
|
||||
assert hass.states.get("sensor.sensor1").state == "0.0"
|
||||
|
||||
# More time passes, and the history stats does a polled update again. It should be 0 since the sensor has been off since midnight.
|
||||
t4 = t3 + timedelta(minutes=10)
|
||||
with freeze_time(t4):
|
||||
async_fire_time_changed(hass, t4)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert hass.states.get("sensor.sensor1").state == "0.0"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("time_zone", ["Europe/Berlin", "America/Chicago", "US/Hawaii"])
|
||||
async def test_end_time_with_microseconds_zeroed(
|
||||
time_zone: str,
|
||||
|
Loading…
x
Reference in New Issue
Block a user