mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-25 18:26:35 +00:00
Convert history header/footer to use streaming history (#15136)
* Add support for streaming history * Add support for streaming history * Add support for streaming history * Add support for streaming history * fixes * cleanup * redraw * naming is hard * drop cached history * backport * Convert history header/footer to use streaming history needs #15112 * Update src/data/history.ts Co-authored-by: Paulus Schoutsen <balloob@gmail.com> * Update src/data/history.ts Co-authored-by: Paulus Schoutsen <balloob@gmail.com> * review * review * review * review * review * review * review * review * adjust * Revert "adjust" This reverts commit 6ba31da4a5a619a0da1bfbcfe18723de595e19aa. * move setInterval Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
This commit is contained in:
parent
815d4c165d
commit
2b2dd74672
@ -1,4 +1,5 @@
|
|||||||
import { strokeWidth } from "../../../../data/graph";
|
import { strokeWidth } from "../../../../data/graph";
|
||||||
|
import { EntityHistoryState } from "../../../../data/history";
|
||||||
|
|
||||||
const average = (items: any[]): number =>
|
const average = (items: any[]): number =>
|
||||||
items.reduce((sum, entry) => sum + parseFloat(entry.state), 0) / items.length;
|
items.reduce((sum, entry) => sum + parseFloat(entry.state), 0) / items.length;
|
||||||
@ -105,3 +106,25 @@ export const coordinates = (
|
|||||||
|
|
||||||
return calcPoints(history, hours, width, detail, min, max);
|
return calcPoints(history, hours, width, detail, min, max);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
interface NumericEntityHistoryState {
|
||||||
|
state: number;
|
||||||
|
last_changed: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const coordinatesMinimalResponseCompressedState = (
|
||||||
|
history: EntityHistoryState[],
|
||||||
|
hours: number,
|
||||||
|
width: number,
|
||||||
|
detail: number,
|
||||||
|
limits?: { min?: number; max?: number }
|
||||||
|
): number[][] | undefined => {
|
||||||
|
const numericHistory: NumericEntityHistoryState[] = history.map((item) => ({
|
||||||
|
state: Number(item.s),
|
||||||
|
// With minimal response and compressed state, we don't have last_changed,
|
||||||
|
// so we use last_updated since its always the same as last_changed since
|
||||||
|
// we already filtered out states that are the same.
|
||||||
|
last_changed: item.lu * 1000,
|
||||||
|
}));
|
||||||
|
return coordinates(numericHistory, hours, width, detail, limits);
|
||||||
|
};
|
||||||
|
@ -9,18 +9,18 @@ import {
|
|||||||
} from "lit";
|
} from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import "../../../components/ha-circular-progress";
|
import "../../../components/ha-circular-progress";
|
||||||
import { fetchRecent } from "../../../data/history";
|
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||||
|
import { subscribeHistoryStatesTimeWindow } from "../../../data/history";
|
||||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||||
import { HomeAssistant } from "../../../types";
|
import { HomeAssistant } from "../../../types";
|
||||||
import { findEntities } from "../common/find-entities";
|
import { findEntities } from "../common/find-entities";
|
||||||
import { coordinates } from "../common/graph/coordinates";
|
import { coordinatesMinimalResponseCompressedState } from "../common/graph/coordinates";
|
||||||
import { hasConfigOrEntityChanged } from "../common/has-changed";
|
|
||||||
import "../components/hui-graph-base";
|
import "../components/hui-graph-base";
|
||||||
import { LovelaceHeaderFooter, LovelaceHeaderFooterEditor } from "../types";
|
import { LovelaceHeaderFooter, LovelaceHeaderFooterEditor } from "../types";
|
||||||
import { GraphHeaderFooterConfig } from "./types";
|
import { GraphHeaderFooterConfig } from "./types";
|
||||||
|
|
||||||
const MINUTE = 60000;
|
const MINUTE = 60000;
|
||||||
const HOUR = MINUTE * 60;
|
const HOUR = 60 * MINUTE;
|
||||||
const includeDomains = ["counter", "input_number", "number", "sensor"];
|
const includeDomains = ["counter", "input_number", "number", "sensor"];
|
||||||
|
|
||||||
@customElement("hui-graph-header-footer")
|
@customElement("hui-graph-header-footer")
|
||||||
@ -66,11 +66,11 @@ export class HuiGraphHeaderFooter
|
|||||||
|
|
||||||
@state() private _coordinates?: number[][];
|
@state() private _coordinates?: number[][];
|
||||||
|
|
||||||
private _date?: Date;
|
private _error?: string;
|
||||||
|
|
||||||
private _stateHistory?: HassEntity[];
|
private _interval?: number;
|
||||||
|
|
||||||
private _fetching = false;
|
private _subscribed?: Promise<(() => Promise<void>) | void>;
|
||||||
|
|
||||||
public getCardSize(): number {
|
public getCardSize(): number {
|
||||||
return 3;
|
return 3;
|
||||||
@ -104,6 +104,10 @@ export class HuiGraphHeaderFooter
|
|||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this._error) {
|
||||||
|
return html`<div class="errors">${this._error}</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
if (!this._coordinates) {
|
if (!this._coordinates) {
|
||||||
return html`
|
return html`
|
||||||
<div class="container">
|
<div class="container">
|
||||||
@ -125,89 +129,91 @@ export class HuiGraphHeaderFooter
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected shouldUpdate(changedProps: PropertyValues): boolean {
|
public connectedCallback() {
|
||||||
return hasConfigOrEntityChanged(this, changedProps);
|
super.connectedCallback();
|
||||||
|
if (this.hasUpdated) {
|
||||||
|
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._coordinates =
|
||||||
|
coordinatesMinimalResponseCompressedState(
|
||||||
|
combinedHistory[this._config!.entity],
|
||||||
|
this._config!.hours_to_show!,
|
||||||
|
500,
|
||||||
|
this._config!.detail!,
|
||||||
|
this._config!.limits
|
||||||
|
) || [];
|
||||||
|
},
|
||||||
|
this._config!.hours_to_show!,
|
||||||
|
[this._config!.entity]
|
||||||
|
).catch((err) => {
|
||||||
|
this._subscribed = undefined;
|
||||||
|
this._error = err;
|
||||||
|
});
|
||||||
|
this._setRedrawTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _redrawGraph() {
|
||||||
|
if (this._coordinates) {
|
||||||
|
this._coordinates = [...this._coordinates];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _setRedrawTimer() {
|
||||||
|
// redraw the graph every minute to update the time axis
|
||||||
|
clearInterval(this._interval);
|
||||||
|
this._interval = window.setInterval(
|
||||||
|
() => this._redrawGraph(),
|
||||||
|
this._config!.hours_to_show! > 24 ? HOUR : MINUTE
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _unsubscribeHistoryTimeWindow() {
|
||||||
|
clearInterval(this._interval);
|
||||||
|
if (!this._subscribed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._subscribed.then((unsubscribe) => {
|
||||||
|
if (unsubscribe) {
|
||||||
|
unsubscribe();
|
||||||
|
}
|
||||||
|
this._subscribed = undefined;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected updated(changedProps: PropertyValues) {
|
protected updated(changedProps: PropertyValues) {
|
||||||
if (
|
if (!this._config || !this.hass || !changedProps.has("_config")) {
|
||||||
!this._config ||
|
|
||||||
!this.hass ||
|
|
||||||
(this._fetching && !changedProps.has("_config"))
|
|
||||||
) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (changedProps.has("_config")) {
|
const oldConfig = changedProps.get("_config") as GraphHeaderFooterConfig;
|
||||||
const oldConfig = changedProps.get("_config") as GraphHeaderFooterConfig;
|
if (
|
||||||
if (!oldConfig || oldConfig.entity !== this._config.entity) {
|
!oldConfig ||
|
||||||
this._stateHistory = [];
|
!this._subscribed ||
|
||||||
}
|
oldConfig.entity !== this._config.entity
|
||||||
|
) {
|
||||||
this._getCoordinates();
|
this._unsubscribeHistoryTimeWindow();
|
||||||
} else if (Date.now() - this._date!.getTime() >= MINUTE) {
|
this._subscribeHistoryTimeWindow();
|
||||||
this._getCoordinates();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _getCoordinates(): Promise<void> {
|
|
||||||
this._fetching = true;
|
|
||||||
const endTime = new Date();
|
|
||||||
const startTime =
|
|
||||||
!this._date || !this._stateHistory?.length
|
|
||||||
? new Date(
|
|
||||||
new Date().setHours(
|
|
||||||
endTime.getHours() - this._config!.hours_to_show!
|
|
||||||
)
|
|
||||||
)
|
|
||||||
: this._date;
|
|
||||||
|
|
||||||
if (this._stateHistory!.length) {
|
|
||||||
const inHoursToShow: HassEntity[] = [];
|
|
||||||
const outHoursToShow: HassEntity[] = [];
|
|
||||||
// Split into inside and outside of "hours to show".
|
|
||||||
this._stateHistory!.forEach((entity) =>
|
|
||||||
(endTime.getTime() - new Date(entity.last_changed).getTime() <=
|
|
||||||
this._config!.hours_to_show! * HOUR
|
|
||||||
? inHoursToShow
|
|
||||||
: outHoursToShow
|
|
||||||
).push(entity)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (outHoursToShow.length) {
|
|
||||||
// If we have values that are now outside of "hours to show", re-add the last entry. This could e.g. be
|
|
||||||
// the "initial state" from the history backend. Without it, it would look like there is no history data
|
|
||||||
// at the start at all in the database = graph would start suddenly instead of on the left side of the card.
|
|
||||||
inHoursToShow.push(outHoursToShow[outHoursToShow.length - 1]);
|
|
||||||
}
|
|
||||||
this._stateHistory = inHoursToShow;
|
|
||||||
}
|
|
||||||
|
|
||||||
const stateHistory = await fetchRecent(
|
|
||||||
this.hass!,
|
|
||||||
this._config!.entity,
|
|
||||||
startTime,
|
|
||||||
endTime,
|
|
||||||
Boolean(this._stateHistory!.length)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (stateHistory.length && stateHistory[0].length) {
|
|
||||||
this._stateHistory!.push(...stateHistory[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
this._coordinates =
|
|
||||||
coordinates(
|
|
||||||
this._stateHistory,
|
|
||||||
this._config!.hours_to_show!,
|
|
||||||
500,
|
|
||||||
this._config!.detail!,
|
|
||||||
this._config!.limits
|
|
||||||
) || [];
|
|
||||||
|
|
||||||
this._date = endTime;
|
|
||||||
this._fetching = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return css`
|
return css`
|
||||||
ha-circular-progress {
|
ha-circular-progress {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user