diff --git a/src/components/chart/state-history-chart-timeline.ts b/src/components/chart/state-history-chart-timeline.ts
index 7b5a25e1ae..546e062d29 100644
--- a/src/components/chart/state-history-chart-timeline.ts
+++ b/src/components/chart/state-history-chart-timeline.ts
@@ -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)
diff --git a/src/components/chart/state-history-charts.ts b/src/components/chart/state-history-charts.ts
index 19421ef9b2..3d3d06a167 100644
--- a/src/components/chart/state-history-charts.ts
+++ b/src/components/chart/state-history-charts.ts
@@ -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`
diff --git a/src/data/history.ts b/src/data/history.ts
index b4eb5e87ba..b0d1d4b041 100644
--- a/src/data/history.ts
+++ b/src/data/history.ts
@@ -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(
- (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
);
diff --git a/src/panels/history/ha-panel-history.ts b/src/panels/history/ha-panel-history.ts
index f78ca6b0ec..b3be7664b4 100644
--- a/src/panels/history/ha-panel-history.ts
+++ b/src/panels/history/ha-panel-history.ts
@@ -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;
+
+ 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;
}
}