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

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;

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 {