Add streaming to history panel (#15301)

This commit is contained in:
Bram Kragten 2023-02-01 17:04:35 +01:00 committed by GitHub
parent 57289b0bbe
commit 4bce4152d3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 97 additions and 29 deletions

View File

@ -64,6 +64,8 @@ export class StateHistoryChartTimeline extends LitElement {
}
if (
changedProps.has("startTime") ||
changedProps.has("endTime") ||
changedProps.has("data") ||
this._chartTime <
new Date(this.endTime.getTime() - MIN_TIME_BETWEEN_UPDATES)

View File

@ -38,7 +38,7 @@ declare global {
}
@customElement("state-history-charts")
class StateHistoryCharts extends LitElement {
export class StateHistoryCharts extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public historyData!: HistoryResult;
@ -71,7 +71,6 @@ class StateHistoryCharts extends LitElement {
// @ts-ignore
@restoreScroll(".container") private _savedScrollPos?: number;
@eventOptions({ passive: true })
protected render(): TemplateResult {
if (!isComponentLoaded(this.hass, "history")) {
return html`<div class="info">

View File

@ -117,7 +117,7 @@ export const fetchDateWS = (
export const subscribeHistory = (
hass: HomeAssistant,
callbackFunction: (message: HistoryStreamMessage) => void,
callbackFunction: (data: HistoryStates) => void,
startTime: Date,
endTime: Date,
entityIds: string[]
@ -132,8 +132,9 @@ export const subscribeHistory = (
entityIdHistoryNeedsAttributes(hass, entityId)
),
};
const stream = new HistoryStream(hass);
return hass.connection.subscribeMessage<HistoryStreamMessage>(
(message) => callbackFunction(message),
(message) => callbackFunction(stream.processMessage(message)),
params
);
};
@ -141,11 +142,11 @@ export const subscribeHistory = (
class HistoryStream {
hass: HomeAssistant;
hoursToShow: number;
hoursToShow?: number;
combinedHistory: HistoryStates;
constructor(hass: HomeAssistant, hoursToShow: number) {
constructor(hass: HomeAssistant, hoursToShow?: number) {
this.hass = hass;
this.hoursToShow = hoursToShow;
this.combinedHistory = {};
@ -161,8 +162,9 @@ class HistoryStream {
// indicate no more historical events
return this.combinedHistory;
}
const purgeBeforePythonTime =
(new Date().getTime() - 60 * 60 * this.hoursToShow * 1000) / 1000;
const purgeBeforePythonTime = this.hoursToShow
? (new Date().getTime() - 60 * 60 * this.hoursToShow * 1000) / 1000
: undefined;
const newHistory: HistoryStates = {};
for (const entityId of Object.keys(this.combinedHistory)) {
newHistory[entityId] = [];
@ -195,7 +197,7 @@ class HistoryStream {
newHistory[entityId] = streamMessage.states[entityId];
}
// Remove old history
if (entityId in this.combinedHistory) {
if (purgeBeforePythonTime && entityId in this.combinedHistory) {
const expiredStates = newHistory[entityId].filter(
(state) => state.lu < purgeBeforePythonTime
);

View File

@ -3,6 +3,7 @@ import "@polymer/app-layout/app-header/app-header";
import "@polymer/app-layout/app-toolbar/app-toolbar";
import {
addDays,
differenceInHours,
endOfToday,
endOfWeek,
endOfYesterday,
@ -15,17 +16,19 @@ import {
UnsubscribeFunc,
} from "home-assistant-js-websocket/dist/types";
import { css, html, LitElement, PropertyValues } from "lit";
import { property, state } from "lit/decorators";
import { property, query, state } from "lit/decorators";
import { ensureArray } from "../../common/array/ensure-array";
import { firstWeekdayIndex } from "../../common/datetime/first_weekday";
import { LocalStorage } from "../../common/decorators/local-storage";
import { ensureArray } from "../../common/array/ensure-array";
import { navigate } from "../../common/navigate";
import {
createSearchParam,
extractSearchParamsObject,
} from "../../common/url/search-params";
import { computeRTL } from "../../common/util/compute_rtl";
import { MIN_TIME_BETWEEN_UPDATES } from "../../components/chart/ha-chart-base";
import "../../components/chart/state-history-charts";
import type { StateHistoryCharts } from "../../components/chart/state-history-charts";
import "../../components/ha-circular-progress";
import "../../components/ha-date-range-picker";
import type { DateRangePickerRanges } from "../../components/ha-date-range-picker";
@ -44,7 +47,11 @@ import {
subscribeDeviceRegistry,
} from "../../data/device_registry";
import { subscribeEntityRegistry } from "../../data/entity_registry";
import { computeHistory, fetchDateWS } from "../../data/history";
import {
computeHistory,
HistoryResult,
subscribeHistory,
} from "../../data/history";
import "../../layouts/ha-app-layout";
import { SubscribeMixin } from "../../mixins/subscribe-mixin";
import { haStyle } from "../../resources/styles";
@ -66,7 +73,7 @@ class HaPanelHistory extends SubscribeMixin(LitElement) {
@state() private _isLoading = false;
@state() private _stateHistory?;
@state() private _stateHistory?: HistoryResult;
@state() private _ranges?: DateRangePickerRanges;
@ -76,18 +83,37 @@ class HaPanelHistory extends SubscribeMixin(LitElement) {
@state() private _areaDeviceLookup?: AreaDeviceLookup;
@query("state-history-charts")
private _stateHistoryCharts?: StateHistoryCharts;
private _subscribed?: Promise<UnsubscribeFunc>;
private _interval?: number;
public constructor() {
super();
const start = new Date();
start.setHours(start.getHours() - 2, 0, 0, 0);
start.setHours(start.getHours() - 1, 0, 0, 0);
this._startDate = start;
const end = new Date();
end.setHours(end.getHours() + 1, 0, 0, 0);
end.setHours(end.getHours() + 2, 0, 0, 0);
this._endDate = end;
}
public connectedCallback() {
super.connectedCallback();
if (this.hasUpdated) {
this._getHistory();
}
}
public disconnectedCallback() {
super.disconnectedCallback();
this._unsubscribeHistory();
}
public hassSubscribe(): UnsubscribeFunc[] {
return [
subscribeEntityRegistry(this.hass.connection!, (entities) => {
@ -270,24 +296,63 @@ class HaPanelHistory extends SubscribeMixin(LitElement) {
if (entityIds.length === 0) {
this._isLoading = false;
this._stateHistory = [];
this._stateHistory = { line: [], timeline: [] };
return;
}
try {
const dateHistory = await fetchDateWS(
this.hass,
this._startDate,
this._endDate,
entityIds
);
this._stateHistory = computeHistory(
this.hass,
dateHistory,
this.hass.localize
);
} finally {
if (this._subscribed) {
this._unsubscribeHistory();
}
const now = new Date();
this._subscribed = subscribeHistory(
this.hass,
(history) => {
this._isLoading = false;
this._stateHistory = computeHistory(
this.hass,
history,
this.hass.localize
);
},
this._startDate,
this._endDate,
entityIds
);
this._subscribed.catch(() => {
this._isLoading = false;
this._unsubscribeHistory();
});
if (this._endDate > now) {
this._setRedrawTimer();
}
}
private _setRedrawTimer() {
clearInterval(this._interval);
const now = new Date();
const end = this._endDate > now ? now : this._endDate;
const timespan = differenceInHours(this._startDate, end);
this._interval = window.setInterval(
() => this._stateHistoryCharts?.requestUpdate(),
// if timespan smaller than 1 hour, update every 10 seconds, smaller than 5 hours, redraw every minute, otherwise every 5 minutes
timespan < 2
? 10000
: timespan < 10
? 60 * 1000
: MIN_TIME_BETWEEN_UPDATES
);
}
private _unsubscribeHistory() {
if (this._interval) {
clearInterval(this._interval);
this._interval = undefined;
}
if (this._subscribed) {
this._subscribed.then((unsub) => unsub?.());
this._subscribed = undefined;
}
}