1 """Manage the history_stats data."""
3 from __future__
import annotations
5 from dataclasses
import dataclass
15 from .helpers
import async_calculate_period, floored_timestamp
17 MIN_TIME_UTC = datetime.datetime.min.replace(tzinfo=dt_util.UTC)
19 _LOGGER = logging.getLogger(__name__)
24 """The current stats of the history stats."""
26 seconds_matched: float |
None
27 match_count: int |
None
28 period: tuple[datetime.datetime, datetime.datetime]
33 """A minimal state to avoid holding on to State objects."""
40 """Manage history stats."""
46 entity_states: list[str],
47 start: Template |
None,
49 duration: datetime.timedelta |
None,
51 """Init the history stats manager."""
54 self.
_period_period = (MIN_TIME_UTC, MIN_TIME_UTC)
64 self, event: Event[EventStateChangedData] |
None
65 ) -> HistoryStatsState:
66 """Update the stats at a given time."""
68 previous_period_start, previous_period_end = self.
_period_period
72 current_period_start, current_period_end = self.
_period_period
75 current_period_start = dt_util.as_utc(current_period_start)
76 current_period_end = dt_util.as_utc(current_period_end)
77 previous_period_start = dt_util.as_utc(previous_period_start)
78 previous_period_end = dt_util.as_utc(previous_period_end)
85 utc_now = dt_util.utcnow()
88 if current_period_start_timestamp > now_timestamp:
104 and current_period_start_timestamp == previous_period_start_timestamp
106 current_period_end_timestamp == previous_period_end_timestamp
108 current_period_end_timestamp >= previous_period_end_timestamp
109 and previous_period_end_timestamp <= now_timestamp
114 if event
and (new_state := event.data[
"new_state"])
is not None:
116 current_period_start_timestamp
118 <= current_period_end_timestamp
122 new_state.state, new_state.last_changed.timestamp()
126 if not new_data
and current_period_end_timestamp < now_timestamp:
132 current_period_start_timestamp, current_period_end_timestamp
138 current_period_start_timestamp,
139 current_period_end_timestamp,
146 current_period_start_timestamp: float,
147 current_period_end_timestamp: float,
149 """Update history data for the current period from the database."""
151 states = await instance.async_add_executor_job(
153 current_period_start_timestamp,
154 current_period_end_timestamp,
157 HistoryState(state.state, state.last_changed.timestamp())
162 self, start_ts: float, end_ts: float
164 """Return state changes during a period."""
165 start = dt_util.utc_from_timestamp(start_ts)
166 end = dt_util.utc_from_timestamp(end_ts)
167 return history.state_changes_during_period(
172 include_start_time_state=
True,
177 self, now_timestamp: float, start_timestamp: float, end_timestamp: float
178 ) -> tuple[float, int]:
179 """Compute the seconds matched and changes from the history list and first state."""
183 previous_state_matches =
False
184 last_state_change_timestamp = 0.0
190 current_state_matches = history_state.state
in self.
_entity_states_entity_states
191 state_change_timestamp = history_state.last_changed
193 if math.floor(state_change_timestamp) > now_timestamp:
196 "Skipping future timestamp %s (now %s)",
197 state_change_timestamp,
202 if previous_state_matches:
203 elapsed += state_change_timestamp - last_state_change_timestamp
204 elif current_state_matches:
207 previous_state_matches = current_state_matches
208 last_state_change_timestamp =
max(start_timestamp, state_change_timestamp)
211 if previous_state_matches:
212 measure_end =
min(end_timestamp, now_timestamp)
213 elapsed += measure_end - last_state_change_timestamp
216 seconds_matched = elapsed
217 return seconds_matched, match_count
None __init__(self, HomeAssistant hass, str entity_id, list[str] entity_states, Template|None start, Template|None end, datetime.timedelta|None duration)
_previous_run_before_start
HistoryStatsState async_update(self, Event[EventStateChangedData]|None event)
list[State] _state_changes_during_period(self, float start_ts, float end_ts)
tuple[float, int] _async_compute_seconds_and_changes(self, float now_timestamp, float start_timestamp, float end_timestamp)
None _async_history_from_db(self, float current_period_start_timestamp, float current_period_end_timestamp)
web.Response get(self, web.Request request, str config_key)
tuple[datetime.datetime, datetime.datetime] async_calculate_period(datetime.timedelta|None duration, Template|None start_template, Template|None end_template)
float floored_timestamp(datetime.datetime incoming_dt)
Recorder get_instance(HomeAssistant hass)