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; } }