Compare commits

...

45 Commits

Author SHA1 Message Date
J. Nick Koston d154872955 Merge branch 'remove_legacy_history_calls' into int2 2023-01-21 18:04:34 -10:00
J. Nick Koston 555f0dbc81 Optimize purge of old live history data in the time window
from https://github.com/home-assistant/frontend/pull/15112#discussion_r1083382923
2023-01-21 18:03:53 -10:00
J. Nick Koston 076c58acea Merge branch 'streaming_history_hui-graph-header-footer_maps' into remove_legacy_history_calls 2023-01-21 17:59:32 -10:00
J. Nick Koston c873ea0bf8 Merge remote-tracking branch 'origin/dev' into streaming_history_hui-graph-header-footer_maps 2023-01-21 17:59:15 -10:00
J. Nick Koston 73b49642f8 Merge remote-tracking branch 'origin/streaming_history_hui-graph-header-footer_maps' into remove_legacy_history_calls 2023-01-21 17:54:42 -10:00
J. Nick Koston cfdbad504c refactor 2023-01-21 17:53:38 -10:00
J. Nick Koston 26730cb2e2 Object.keys to for 2023-01-21 17:50:22 -10:00
J. Nick Koston a77589922c Merge branch 'streaming_history_hui-graph-header-footer_maps' into remove_legacy_history_calls 2023-01-21 17:46:08 -10:00
J. Nick Koston 545f81fa24 Merge branch 'streaming_history_hui-graph-header-footer' into streaming_history_hui-graph-header-footer_maps 2023-01-21 17:45:54 -10:00
J. Nick Koston 265ca40df1 move setInterval 2023-01-21 17:45:29 -10:00
J. Nick Koston 58cb775627 Merge branch 'dev' into streaming_history_hui-graph-header-footer 2023-01-21 17:45:14 -10:00
J. Nick Koston 945d992884 Merge branch 'streaming_history_hui-graph-header-footer_maps' into remove_legacy_history_calls 2023-01-21 17:30:10 -10:00
J. Nick Koston 574396f369 Merge branch 'streaming_history_hui-graph-header-footer' into streaming_history_hui-graph-header-footer_maps 2023-01-21 17:29:59 -10:00
J. Nick Koston 42d3adad26 Merge branch 'streaming_history' into streaming_history_hui-graph-header-footer 2023-01-21 17:29:48 -10:00
J. Nick Koston 7923c172ff Revert "adjust"
This reverts commit 6ba31da4a5.
2023-01-21 17:28:42 -10:00
J. Nick Koston ced00a4945 Merge branch 'streaming_history_hui-graph-header-footer_maps' into remove_legacy_history_calls 2023-01-21 17:18:34 -10:00
J. Nick Koston 9c2a72d240 Merge branch 'streaming_history_hui-graph-header-footer' into streaming_history_hui-graph-header-footer_maps 2023-01-21 17:18:18 -10:00
J. Nick Koston 89c2db1f8e Merge branch 'streaming_history' into streaming_history_hui-graph-header-footer 2023-01-21 17:18:05 -10:00
J. Nick Koston 6ba31da4a5 adjust 2023-01-21 17:17:43 -10:00
J. Nick Koston 1c516822a5 Merge branch 'streaming_history_hui-graph-header-footer_maps' into remove_legacy_history_calls 2023-01-21 17:12:13 -10:00
J. Nick Koston be741feb2a Merge branch 'streaming_history_hui-graph-header-footer' into streaming_history_hui-graph-header-footer_maps 2023-01-21 17:11:43 -10:00
J. Nick Koston 72605051e4 Merge branch 'streaming_history' into streaming_history_hui-graph-header-footer 2023-01-21 17:11:25 -10:00
J. Nick Koston 9f9ab6c238 review 2023-01-21 17:10:55 -10:00
J. Nick Koston 1e4a3c398b review 2023-01-21 17:08:03 -10:00
J. Nick Koston 690e2a3aa9 review 2023-01-21 17:06:23 -10:00
J. Nick Koston e1d17f3d3a review 2023-01-21 17:03:39 -10:00
J. Nick Koston 45316b458f review 2023-01-21 17:03:18 -10:00
J. Nick Koston 51a69f1042 review 2023-01-21 17:02:15 -10:00
J. Nick Koston e4ebb320e5 review 2023-01-21 17:01:23 -10:00
J. Nick Koston e9c5e51f25 review 2023-01-21 17:00:40 -10:00
J. Nick Koston 4091205b58 Update src/data/history.ts
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2023-01-21 16:59:33 -10:00
J. Nick Koston 478539d58d Update src/data/history.ts
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2023-01-21 16:59:06 -10:00
J. Nick Koston d57cf65edc Remove unused fetchRecent/fetchRecentWS/fetchDate history data functions
These call are no longer needed after
2023-01-19 14:36:47 -10:00
J. Nick Koston 21f58356bd Update map card to use streaming history
Update map card to use streaming history

Update map card to use streaming history

Update map card to use streaming history

Update map card to use streaming history

Update map card to use streaming history

Update map card to use streaming history
2023-01-19 11:27:59 -10:00
J. Nick Koston 79cb7bda04 Convert history header/footer to use streaming history
needs #15112
2023-01-19 11:27:42 -10:00
J. Nick Koston ec844560af backport 2023-01-19 11:27:05 -10:00
J. Nick Koston c467ef82ea drop cached history 2023-01-16 12:26:23 -10:00
J. Nick Koston bdcd1a0a88 naming is hard 2023-01-16 10:25:39 -10:00
J. Nick Koston 36710d7588 redraw 2023-01-16 10:07:45 -10:00
J. Nick Koston c1e905e03a cleanup 2023-01-15 16:30:39 -10:00
J. Nick Koston 87d781f786 fixes 2023-01-15 16:21:04 -10:00
J. Nick Koston c34e845886 Add support for streaming history 2023-01-15 15:34:25 -10:00
J. Nick Koston 412587a457 Add support for streaming history 2023-01-15 15:06:47 -10:00
J. Nick Koston 5b3a13f8d4 Add support for streaming history 2023-01-15 14:35:06 -10:00
J. Nick Koston 1ec5172370 Add support for streaming history 2023-01-15 14:33:56 -10:00
2 changed files with 119 additions and 183 deletions
+20 -80
View File
@@ -94,73 +94,6 @@ export const entityIdHistoryNeedsAttributes = (
!hass.states[entityId] ||
NEED_ATTRIBUTE_DOMAINS.includes(computeDomain(entityId));
export const fetchRecent = (
hass: HomeAssistant,
entityId: string,
startTime: Date,
endTime: Date,
skipInitialState = false,
significantChangesOnly?: boolean,
minimalResponse = true,
noAttributes?: boolean
): Promise<HassEntity[][]> => {
let url = "history/period";
if (startTime) {
url += "/" + startTime.toISOString();
}
url += "?filter_entity_id=" + entityId;
if (endTime) {
url += "&end_time=" + endTime.toISOString();
}
if (skipInitialState) {
url += "&skip_initial_state";
}
if (significantChangesOnly !== undefined) {
url += `&significant_changes_only=${Number(significantChangesOnly)}`;
}
if (minimalResponse) {
url += "&minimal_response";
}
if (noAttributes) {
url += "&no_attributes";
}
return hass.callApi("GET", url);
};
export const fetchRecentWS = (
hass: HomeAssistant,
entityIds: string[],
startTime: Date,
endTime: Date,
skipInitialState = false,
significantChangesOnly?: boolean,
minimalResponse = true,
noAttributes?: boolean
) =>
hass.callWS<HistoryStates>({
type: "history/history_during_period",
start_time: startTime.toISOString(),
end_time: endTime.toISOString(),
significant_changes_only: significantChangesOnly || false,
include_start_time_state: !skipInitialState,
minimal_response: minimalResponse,
no_attributes: noAttributes || false,
entity_ids: entityIds,
});
export const fetchDate = (
hass: HomeAssistant,
startTime: Date,
endTime: Date,
entityIds: string[]
): Promise<HassEntity[][]> =>
hass.callApi(
"GET",
`history/period/${startTime.toISOString()}?end_time=${endTime.toISOString()}&minimal_response${
entityIds ? `&filter_entity_id=${entityIds.join(",")}` : ``
}`
);
export const fetchDateWS = (
hass: HomeAssistant,
startTime: Date,
@@ -263,20 +196,27 @@ class HistoryStream {
}
// Remove old history
if (entityId in this.combinedHistory) {
const entityHistory = newHistory[entityId];
while (entityHistory[0].lu < purgeBeforePythonTime) {
if (entityHistory.length > 1) {
if (entityHistory[1].lu < purgeBeforePythonTime) {
newHistory[entityId].shift();
continue;
}
}
// Update the first entry to the start time state
// as we need to preserve the start time state and
// only expire the rest of the history as it ages.
entityHistory[0].lu = purgeBeforePythonTime;
break;
const expiredStates = newHistory[entityId].filter(
(state) => state.lu < purgeBeforePythonTime
);
if (!expiredStates.length) {
continue;
}
newHistory[entityId] = newHistory[entityId].filter(
(state) => state.lu >= purgeBeforePythonTime
);
if (
newHistory[entityId].length &&
newHistory[entityId][0].lu === purgeBeforePythonTime
) {
continue;
}
// Update the first entry to the start time state
// as we need to preserve the start time state and
// only expire the rest of the history as it ages.
const lastExpiredState = expiredStates[expiredStates.length - 1];
lastExpiredState.lu = purgeBeforePythonTime;
newHistory[entityId].unshift(lastExpiredState);
}
}
this.combinedHistory = newHistory;
+99 -103
View File
@@ -1,4 +1,4 @@
import { HassEntities, HassEntity } from "home-assistant-js-websocket";
import { HassEntities } from "home-assistant-js-websocket";
import { LatLngTuple } from "leaflet";
import {
css,
@@ -12,11 +12,15 @@ import { customElement, property, query, state } from "lit/decorators";
import { mdiImageFilterCenterFocus } from "@mdi/js";
import memoizeOne from "memoize-one";
import { isToday } from "date-fns";
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
import { computeDomain } from "../../../common/entity/compute_domain";
import parseAspectRatio from "../../../common/util/parse-aspect-ratio";
import "../../../components/ha-card";
import "../../../components/ha-icon-button";
import { fetchRecent } from "../../../data/history";
import {
HistoryStates,
subscribeHistoryStatesTimeWindow,
} from "../../../data/history";
import { HomeAssistant } from "../../../types";
import { findEntities } from "../common/find-entities";
import { processConfigEntities } from "../common/process-config-entities";
@@ -36,8 +40,7 @@ import {
formatTimeWeekday,
} from "../../../common/datetime/format_time";
const MINUTE = 60000;
const DEFAULT_HOURS_TO_SHOW = 24;
@customElement("hui-map-card")
class HuiMapCard extends LitElement implements LovelaceCard {
@property({ attribute: false }) public hass!: HomeAssistant;
@@ -45,8 +48,7 @@ class HuiMapCard extends LitElement implements LovelaceCard {
@property({ type: Boolean, reflect: true })
public isPanel = false;
@state()
private _history?: HassEntity[][];
@state() private _stateHistory?: HistoryStates;
@state()
private _config?: MapCardConfig;
@@ -54,14 +56,16 @@ class HuiMapCard extends LitElement implements LovelaceCard {
@query("ha-map")
private _map?: HaMap;
private _date?: Date;
private _configEntities?: string[];
private _colorDict: Record<string, string> = {};
private _colorIndex = 0;
private _error?: string;
private _subscribed?: Promise<(() => Promise<void>) | void>;
public setConfig(config: MapCardConfig): void {
if (!config) {
throw new Error("Error in card configuration.");
@@ -88,8 +92,6 @@ class HuiMapCard extends LitElement implements LovelaceCard {
? processConfigEntities<EntityConfig>(config.entities)
: []
).map((entity) => entity.entity);
this._cleanupHistory();
}
public getCardSize(): number {
@@ -133,6 +135,9 @@ class HuiMapCard extends LitElement implements LovelaceCard {
if (!this._config) {
return html``;
}
if (this._error) {
return html`<div class="error">${this._error}</div>`;
}
return html`
<ha-card id="card" .header=${this._config.title}>
<div id="root">
@@ -144,7 +149,7 @@ class HuiMapCard extends LitElement implements LovelaceCard {
this._configEntities
)}
.zoom=${this._config.default_zoom ?? 14}
.paths=${this._getHistoryPaths(this._config, this._history)}
.paths=${this._getHistoryPaths(this._config, this._stateHistory)}
.autoFit=${this._config.auto_fit}
.darkMode=${this._config.dark_mode}
></ha-map>
@@ -176,23 +181,68 @@ class HuiMapCard extends LitElement implements LovelaceCard {
return true;
}
// Check if any state has changed
for (const entity of this._configEntities) {
if (oldHass.states[entity] !== this.hass!.states[entity]) {
return true;
}
if (changedProps.has("_stateHistory")) {
return true;
}
return false;
}
protected updated(changedProps: PropertyValues): void {
if (this._config?.hours_to_show && this._configEntities?.length) {
if (changedProps.has("_config")) {
this._getHistory();
} else if (Date.now() - this._date!.getTime() >= MINUTE) {
this._getHistory();
public connectedCallback() {
super.connectedCallback();
if (this.hasUpdated && this._configEntities?.length) {
this._subscribeHistoryTimeWindow();
}
}
public disconnectedCallback() {
super.disconnectedCallback();
this._unsubscribeHistoryTimeWindow();
}
private _subscribeHistoryTimeWindow() {
if (!isComponentLoaded(this.hass!, "history") || this._subscribed) {
return;
}
this._subscribed = subscribeHistoryStatesTimeWindow(
this.hass!,
(combinedHistory) => {
if (!this._subscribed) {
// Message came in before we had a chance to unload
return;
}
this._stateHistory = combinedHistory;
},
this._config!.hours_to_show! || DEFAULT_HOURS_TO_SHOW,
this._configEntities!,
false,
false
).catch((err) => {
this._subscribed = undefined;
this._error = err;
});
}
private _unsubscribeHistoryTimeWindow() {
if (!this._subscribed) {
return;
}
this._subscribed.then((unsubscribe) => {
if (unsubscribe) {
unsubscribe();
}
this._subscribed = undefined;
});
}
protected updated(changedProps: PropertyValues): void {
if (this._configEntities?.length) {
if (!this._subscribed || changedProps.has("_config")) {
this._unsubscribeHistoryTimeWindow();
this._subscribeHistoryTimeWindow();
}
} else {
this._unsubscribeHistoryTimeWindow();
}
if (changedProps.has("_config")) {
this._computePadding();
@@ -272,46 +322,44 @@ class HuiMapCard extends LitElement implements LovelaceCard {
private _getHistoryPaths = memoizeOne(
(
config: MapCardConfig,
history?: HassEntity[][]
history?: HistoryStates
): HaMapPaths[] | undefined => {
if (!config.hours_to_show || !history) {
if (!history) {
return undefined;
}
const paths: HaMapPaths[] = [];
for (const entityStates of history) {
if (entityStates?.length <= 1) {
for (const entityId of Object.keys(history)) {
const entityStates = history[entityId];
if (!entityStates?.length) {
continue;
}
// filter location data from states and remove all invalid locations
const points = entityStates.reduce(
(accumulator: HaMapPathPoint[], entityState) => {
const latitude = entityState.attributes.latitude;
const longitude = entityState.attributes.longitude;
if (latitude && longitude) {
const p = {} as HaMapPathPoint;
p.point = [latitude, longitude] as LatLngTuple;
const t = new Date(entityState.last_updated);
if (config.hours_to_show! > 144) {
// if showing > 6 days in the history trail, show the full
// date and time
p.tooltip = formatDateTime(t, this.hass.locale);
} else if (isToday(t)) {
p.tooltip = formatTime(t, this.hass.locale);
} else {
p.tooltip = formatTimeWeekday(t, this.hass.locale);
}
accumulator.push(p);
}
return accumulator;
},
[]
) as HaMapPathPoint[];
const points: HaMapPathPoint[] = [];
for (const entityState of entityStates) {
const latitude = entityState.a.latitude;
const longitude = entityState.a.longitude;
if (!latitude || !longitude) {
continue;
}
const p = {} as HaMapPathPoint;
p.point = [latitude, longitude] as LatLngTuple;
const t = new Date(entityState.lu * 1000);
if (config.hours_to_show! || DEFAULT_HOURS_TO_SHOW > 144) {
// if showing > 6 days in the history trail, show the full
// date and time
p.tooltip = formatDateTime(t, this.hass.locale);
} else if (isToday(t)) {
p.tooltip = formatTime(t, this.hass.locale);
} else {
p.tooltip = formatTimeWeekday(t, this.hass.locale);
}
points.push(p);
}
paths.push({
points,
color: this._getColor(entityStates[0].entity_id),
color: this._getColor(entityId),
gradualOpacity: 0.8,
});
}
@@ -319,58 +367,6 @@ class HuiMapCard extends LitElement implements LovelaceCard {
}
);
private async _getHistory(): Promise<void> {
this._date = new Date();
if (!this._configEntities) {
return;
}
const entityIds = this._configEntities!.join(",");
const endTime = new Date();
const startTime = new Date();
startTime.setHours(endTime.getHours() - this._config!.hours_to_show!);
const skipInitialState = false;
const significantChangesOnly = false;
const minimalResponse = false;
const stateHistory = await fetchRecent(
this.hass,
entityIds,
startTime,
endTime,
skipInitialState,
significantChangesOnly,
minimalResponse
);
if (stateHistory.length < 1) {
return;
}
this._history = stateHistory;
}
private _cleanupHistory() {
if (!this._history) {
return;
}
if (this._config!.hours_to_show! <= 0) {
this._history = undefined;
} else {
// remove unused entities
this._history = this._history!.reduce(
(accumulator: HassEntity[][], entityStates) => {
const entityId = entityStates[0].entity_id;
if (this._configEntities?.includes(entityId)) {
accumulator.push(entityStates);
}
return accumulator;
},
[]
) as HassEntity[][];
}
}
static get styles(): CSSResultGroup {
return css`
ha-card {