mirror of
https://github.com/home-assistant/frontend.git
synced 2025-08-07 00:17:46 +00:00
Add support for streaming history
This commit is contained in:
parent
412587a457
commit
c34e845886
@ -187,9 +187,8 @@ export const subscribeHistory = (
|
|||||||
endTime: Date,
|
endTime: Date,
|
||||||
entityIds: string[]
|
entityIds: string[]
|
||||||
): Promise<() => Promise<void>> => {
|
): Promise<() => Promise<void>> => {
|
||||||
// If all specified filters are empty lists, we can return an empty list.
|
|
||||||
const params = {
|
const params = {
|
||||||
type: "history/history_during_period",
|
type: "history/stream",
|
||||||
start_time: startTime.toISOString(),
|
start_time: startTime.toISOString(),
|
||||||
end_time: endTime.toISOString(),
|
end_time: endTime.toISOString(),
|
||||||
minimal_response: true,
|
minimal_response: true,
|
||||||
@ -206,16 +205,13 @@ export const subscribeHistory = (
|
|||||||
class HistoryStream {
|
class HistoryStream {
|
||||||
hass: HomeAssistant;
|
hass: HomeAssistant;
|
||||||
|
|
||||||
startTime: Date;
|
hoursToShow: number;
|
||||||
|
|
||||||
endTime: Date;
|
|
||||||
|
|
||||||
combinedHistory: HistoryStates;
|
combinedHistory: HistoryStates;
|
||||||
|
|
||||||
constructor(hass: HomeAssistant, startTime: Date, endTime: Date) {
|
constructor(hass: HomeAssistant, hoursToShow: number) {
|
||||||
this.hass = hass;
|
this.hass = hass;
|
||||||
this.startTime = startTime;
|
this.hoursToShow = hoursToShow;
|
||||||
this.endTime = endTime;
|
|
||||||
this.combinedHistory = {};
|
this.combinedHistory = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -231,7 +227,8 @@ class HistoryStream {
|
|||||||
// indicate no more historical events
|
// indicate no more historical events
|
||||||
return this.combinedHistory;
|
return this.combinedHistory;
|
||||||
}
|
}
|
||||||
const purgeBeforePythonTime = this.startTime.getTime() / 1000;
|
const purgeBeforePythonTime =
|
||||||
|
(new Date().getTime() - 60 * 60 * this.hoursToShow * 1000) / 1000;
|
||||||
const newHistory: HistoryStates = {};
|
const newHistory: HistoryStates = {};
|
||||||
Object.keys(streamMessage.states).forEach((entityId) => {
|
Object.keys(streamMessage.states).forEach((entityId) => {
|
||||||
newHistory[entityId] = [];
|
newHistory[entityId] = [];
|
||||||
@ -260,24 +257,23 @@ class HistoryStream {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const subscribeHistoryStates = (
|
export const subscribeHistoryStatesWindow = (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
callbackFunction: (data: HistoryStates) => void,
|
callbackFunction: (data: HistoryStates) => void,
|
||||||
startTime: Date,
|
hoursToShow: number,
|
||||||
endTime: Date,
|
|
||||||
entityIds: string[]
|
entityIds: string[]
|
||||||
): Promise<() => Promise<void>> => {
|
): Promise<() => Promise<void>> => {
|
||||||
// If all specified filters are empty lists, we can return an empty list.
|
|
||||||
const params = {
|
const params = {
|
||||||
type: "history/history_during_period",
|
type: "history/stream",
|
||||||
start_time: startTime.toISOString(),
|
start_time: new Date(
|
||||||
end_time: endTime.toISOString(),
|
new Date().getTime() - 60 * 60 * hoursToShow * 1000
|
||||||
|
).toISOString(),
|
||||||
minimal_response: true,
|
minimal_response: true,
|
||||||
no_attributes: !entityIds.some((entityId) =>
|
no_attributes: !entityIds.some((entityId) =>
|
||||||
entityIdHistoryNeedsAttributes(hass, entityId)
|
entityIdHistoryNeedsAttributes(hass, entityId)
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
const stream = new HistoryStream(hass, startTime, endTime);
|
const stream = new HistoryStream(hass, hoursToShow);
|
||||||
return hass.connection.subscribeMessage<HistoryStreamMessage>(
|
return hass.connection.subscribeMessage<HistoryStreamMessage>(
|
||||||
(message) => callbackFunction(stream.processMessage(message)),
|
(message) => callbackFunction(stream.processMessage(message)),
|
||||||
params
|
params
|
||||||
|
@ -8,11 +8,13 @@ import {
|
|||||||
} from "lit";
|
} from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { classMap } from "lit/directives/class-map";
|
import { classMap } from "lit/directives/class-map";
|
||||||
import { throttle } from "../../../common/util/throttle";
|
|
||||||
import "../../../components/ha-card";
|
import "../../../components/ha-card";
|
||||||
import "../../../components/chart/state-history-charts";
|
import "../../../components/chart/state-history-charts";
|
||||||
import { CacheConfig, getRecentWithCache } from "../../../data/cached-history";
|
import {
|
||||||
import { HistoryResult } from "../../../data/history";
|
HistoryResult,
|
||||||
|
subscribeHistoryStatesWindow,
|
||||||
|
computeHistory,
|
||||||
|
} from "../../../data/history";
|
||||||
import { HomeAssistant } from "../../../types";
|
import { HomeAssistant } from "../../../types";
|
||||||
import { hasConfigOrEntitiesChanged } from "../common/has-changed";
|
import { hasConfigOrEntitiesChanged } from "../common/has-changed";
|
||||||
import { processConfigEntities } from "../common/process-config-entities";
|
import { processConfigEntities } from "../common/process-config-entities";
|
||||||
@ -42,11 +44,13 @@ export class HuiHistoryGraphCard extends LitElement implements LovelaceCard {
|
|||||||
|
|
||||||
private _names: Record<string, string> = {};
|
private _names: Record<string, string> = {};
|
||||||
|
|
||||||
private _cacheConfig?: CacheConfig;
|
private _entityIds: string[] = [];
|
||||||
|
|
||||||
private _fetching = false;
|
private _hoursToShow = 24;
|
||||||
|
|
||||||
private _throttleGetStateHistory?: () => void;
|
private _error?: string;
|
||||||
|
|
||||||
|
private _subscribed?: Promise<(() => Promise<void>) | void>;
|
||||||
|
|
||||||
public getCardSize(): number {
|
public getCardSize(): number {
|
||||||
return this._config?.title
|
return this._config?.title
|
||||||
@ -67,27 +71,71 @@ export class HuiHistoryGraphCard extends LitElement implements LovelaceCard {
|
|||||||
? processConfigEntities(config.entities)
|
? processConfigEntities(config.entities)
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
const _entities: string[] = [];
|
|
||||||
|
|
||||||
this._configEntities.forEach((entity) => {
|
this._configEntities.forEach((entity) => {
|
||||||
_entities.push(entity.entity);
|
this._entityIds.push(entity.entity);
|
||||||
if (entity.name) {
|
if (entity.name) {
|
||||||
this._names[entity.entity] = entity.name;
|
this._names[entity.entity] = entity.name;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this._throttleGetStateHistory = throttle(() => {
|
this._hoursToShow = config.hours_to_show || 24;
|
||||||
this._getStateHistory();
|
|
||||||
}, config.refresh_interval || 10 * 1000);
|
|
||||||
|
|
||||||
this._cacheConfig = {
|
|
||||||
cacheKey: _entities.join(),
|
|
||||||
hoursToShow: config.hours_to_show || 24,
|
|
||||||
};
|
|
||||||
|
|
||||||
this._config = config;
|
this._config = config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public connectedCallback() {
|
||||||
|
super.connectedCallback();
|
||||||
|
if (this.hasUpdated) {
|
||||||
|
this._subscribeHistoryWindow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public disconnectedCallback() {
|
||||||
|
super.disconnectedCallback();
|
||||||
|
this._unsubscribeHistoryWindow();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _subscribeHistoryWindow() {
|
||||||
|
if (this._subscribed) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
this._subscribed = subscribeHistoryStatesWindow(
|
||||||
|
this.hass!,
|
||||||
|
(combinedHistory) => {
|
||||||
|
// "recent" means start time is a sliding window
|
||||||
|
// so we need to calculate an expireTime to
|
||||||
|
// purge old events
|
||||||
|
if (!this._subscribed) {
|
||||||
|
// Message came in before we had a chance to unload
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._stateHistory = computeHistory(
|
||||||
|
this.hass!,
|
||||||
|
combinedHistory,
|
||||||
|
this.hass!.localize
|
||||||
|
);
|
||||||
|
},
|
||||||
|
this._hoursToShow,
|
||||||
|
this._entityIds
|
||||||
|
).catch((err) => {
|
||||||
|
this._subscribed = undefined;
|
||||||
|
this._error = err;
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _unsubscribeHistoryWindow() {
|
||||||
|
if (!this._subscribed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._subscribed.then((unsubscribe) => {
|
||||||
|
if (unsubscribe) {
|
||||||
|
unsubscribe();
|
||||||
|
}
|
||||||
|
this._subscribed = undefined;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
protected shouldUpdate(changedProps: PropertyValues): boolean {
|
protected shouldUpdate(changedProps: PropertyValues): boolean {
|
||||||
if (changedProps.has("_stateHistory")) {
|
if (changedProps.has("_stateHistory")) {
|
||||||
return true;
|
return true;
|
||||||
@ -100,8 +148,8 @@ export class HuiHistoryGraphCard extends LitElement implements LovelaceCard {
|
|||||||
if (
|
if (
|
||||||
!this._config ||
|
!this._config ||
|
||||||
!this.hass ||
|
!this.hass ||
|
||||||
!this._throttleGetStateHistory ||
|
!this._hoursToShow ||
|
||||||
!this._cacheConfig
|
!this._entityIds.length
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -117,12 +165,10 @@ export class HuiHistoryGraphCard extends LitElement implements LovelaceCard {
|
|||||||
if (
|
if (
|
||||||
changedProps.has("_config") &&
|
changedProps.has("_config") &&
|
||||||
(oldConfig?.entities !== this._config.entities ||
|
(oldConfig?.entities !== this._config.entities ||
|
||||||
oldConfig?.hours_to_show !== this._config.hours_to_show)
|
oldConfig?.hours_to_show !== this._hoursToShow)
|
||||||
) {
|
) {
|
||||||
this._throttleGetStateHistory();
|
this._unsubscribeHistoryWindow();
|
||||||
} else if (changedProps.has("hass")) {
|
this._subscribeHistoryWindow();
|
||||||
// wait for commit of data (we only account for the default setting of 1 sec)
|
|
||||||
setTimeout(this._throttleGetStateHistory, 1000);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,6 +177,10 @@ export class HuiHistoryGraphCard extends LitElement implements LovelaceCard {
|
|||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this._error) {
|
||||||
|
return html`<div class="errors">${this._error}</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-card .header=${this._config.title}>
|
<ha-card .header=${this._config.title}>
|
||||||
<div
|
<div
|
||||||
@ -153,26 +203,6 @@ export class HuiHistoryGraphCard extends LitElement implements LovelaceCard {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _getStateHistory(): Promise<void> {
|
|
||||||
if (this._fetching) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this._fetching = true;
|
|
||||||
try {
|
|
||||||
this._stateHistory = {
|
|
||||||
...(await getRecentWithCache(
|
|
||||||
this.hass!,
|
|
||||||
this._configEntities!.map((config) => config.entity),
|
|
||||||
this._cacheConfig!,
|
|
||||||
this.hass!.localize,
|
|
||||||
this.hass!.language
|
|
||||||
)),
|
|
||||||
};
|
|
||||||
} finally {
|
|
||||||
this._fetching = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return css`
|
return css`
|
||||||
ha-card {
|
ha-card {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user