mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-30 20:56:36 +00:00
Merge branch 'remove_legacy_history_calls' into int2
This commit is contained in:
commit
d154872955
@ -94,73 +94,6 @@ export const entityIdHistoryNeedsAttributes = (
|
|||||||
!hass.states[entityId] ||
|
!hass.states[entityId] ||
|
||||||
NEED_ATTRIBUTE_DOMAINS.includes(computeDomain(entityId));
|
NEED_ATTRIBUTE_DOMAINS.includes(computeDomain(entityId));
|
||||||
|
|
||||||
export const fetchRecent = (
|
|
||||||
hass: HomeAssistant,
|
|
||||||
entityId: string,
|
|
||||||
startTime: Date,
|
|
||||||
endTime: Date,
|
|
||||||
skipInitialState = false,
|
|
||||||
significantChangesOnly?: boolean,
|
|
||||||
minimalResponse = true,
|
|
||||||
noAttributes?: boolean
|
|
||||||
): Promise<HassEntity[][]> => {
|
|
||||||
let url = "history/period";
|
|
||||||
if (startTime) {
|
|
||||||
url += "/" + startTime.toISOString();
|
|
||||||
}
|
|
||||||
url += "?filter_entity_id=" + entityId;
|
|
||||||
if (endTime) {
|
|
||||||
url += "&end_time=" + endTime.toISOString();
|
|
||||||
}
|
|
||||||
if (skipInitialState) {
|
|
||||||
url += "&skip_initial_state";
|
|
||||||
}
|
|
||||||
if (significantChangesOnly !== undefined) {
|
|
||||||
url += `&significant_changes_only=${Number(significantChangesOnly)}`;
|
|
||||||
}
|
|
||||||
if (minimalResponse) {
|
|
||||||
url += "&minimal_response";
|
|
||||||
}
|
|
||||||
if (noAttributes) {
|
|
||||||
url += "&no_attributes";
|
|
||||||
}
|
|
||||||
return hass.callApi("GET", url);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const fetchRecentWS = (
|
|
||||||
hass: HomeAssistant,
|
|
||||||
entityIds: string[],
|
|
||||||
startTime: Date,
|
|
||||||
endTime: Date,
|
|
||||||
skipInitialState = false,
|
|
||||||
significantChangesOnly?: boolean,
|
|
||||||
minimalResponse = true,
|
|
||||||
noAttributes?: boolean
|
|
||||||
) =>
|
|
||||||
hass.callWS<HistoryStates>({
|
|
||||||
type: "history/history_during_period",
|
|
||||||
start_time: startTime.toISOString(),
|
|
||||||
end_time: endTime.toISOString(),
|
|
||||||
significant_changes_only: significantChangesOnly || false,
|
|
||||||
include_start_time_state: !skipInitialState,
|
|
||||||
minimal_response: minimalResponse,
|
|
||||||
no_attributes: noAttributes || false,
|
|
||||||
entity_ids: entityIds,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const fetchDate = (
|
|
||||||
hass: HomeAssistant,
|
|
||||||
startTime: Date,
|
|
||||||
endTime: Date,
|
|
||||||
entityIds: string[]
|
|
||||||
): Promise<HassEntity[][]> =>
|
|
||||||
hass.callApi(
|
|
||||||
"GET",
|
|
||||||
`history/period/${startTime.toISOString()}?end_time=${endTime.toISOString()}&minimal_response${
|
|
||||||
entityIds ? `&filter_entity_id=${entityIds.join(",")}` : ``
|
|
||||||
}`
|
|
||||||
);
|
|
||||||
|
|
||||||
export const fetchDateWS = (
|
export const fetchDateWS = (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
startTime: Date,
|
startTime: Date,
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { HassEntities, HassEntity } from "home-assistant-js-websocket";
|
import { HassEntities } from "home-assistant-js-websocket";
|
||||||
import { LatLngTuple } from "leaflet";
|
import { LatLngTuple } from "leaflet";
|
||||||
import {
|
import {
|
||||||
css,
|
css,
|
||||||
@ -12,11 +12,15 @@ import { customElement, property, query, state } from "lit/decorators";
|
|||||||
import { mdiImageFilterCenterFocus } from "@mdi/js";
|
import { mdiImageFilterCenterFocus } from "@mdi/js";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { isToday } from "date-fns";
|
import { isToday } from "date-fns";
|
||||||
|
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||||
import parseAspectRatio from "../../../common/util/parse-aspect-ratio";
|
import parseAspectRatio from "../../../common/util/parse-aspect-ratio";
|
||||||
import "../../../components/ha-card";
|
import "../../../components/ha-card";
|
||||||
import "../../../components/ha-icon-button";
|
import "../../../components/ha-icon-button";
|
||||||
import { fetchRecent } from "../../../data/history";
|
import {
|
||||||
|
HistoryStates,
|
||||||
|
subscribeHistoryStatesTimeWindow,
|
||||||
|
} from "../../../data/history";
|
||||||
import { HomeAssistant } from "../../../types";
|
import { HomeAssistant } from "../../../types";
|
||||||
import { findEntities } from "../common/find-entities";
|
import { findEntities } from "../common/find-entities";
|
||||||
import { processConfigEntities } from "../common/process-config-entities";
|
import { processConfigEntities } from "../common/process-config-entities";
|
||||||
@ -36,8 +40,7 @@ import {
|
|||||||
formatTimeWeekday,
|
formatTimeWeekday,
|
||||||
} from "../../../common/datetime/format_time";
|
} from "../../../common/datetime/format_time";
|
||||||
|
|
||||||
const MINUTE = 60000;
|
const DEFAULT_HOURS_TO_SHOW = 24;
|
||||||
|
|
||||||
@customElement("hui-map-card")
|
@customElement("hui-map-card")
|
||||||
class HuiMapCard extends LitElement implements LovelaceCard {
|
class HuiMapCard extends LitElement implements LovelaceCard {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
@ -45,8 +48,7 @@ class HuiMapCard extends LitElement implements LovelaceCard {
|
|||||||
@property({ type: Boolean, reflect: true })
|
@property({ type: Boolean, reflect: true })
|
||||||
public isPanel = false;
|
public isPanel = false;
|
||||||
|
|
||||||
@state()
|
@state() private _stateHistory?: HistoryStates;
|
||||||
private _history?: HassEntity[][];
|
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
private _config?: MapCardConfig;
|
private _config?: MapCardConfig;
|
||||||
@ -54,14 +56,16 @@ class HuiMapCard extends LitElement implements LovelaceCard {
|
|||||||
@query("ha-map")
|
@query("ha-map")
|
||||||
private _map?: HaMap;
|
private _map?: HaMap;
|
||||||
|
|
||||||
private _date?: Date;
|
|
||||||
|
|
||||||
private _configEntities?: string[];
|
private _configEntities?: string[];
|
||||||
|
|
||||||
private _colorDict: Record<string, string> = {};
|
private _colorDict: Record<string, string> = {};
|
||||||
|
|
||||||
private _colorIndex = 0;
|
private _colorIndex = 0;
|
||||||
|
|
||||||
|
private _error?: string;
|
||||||
|
|
||||||
|
private _subscribed?: Promise<(() => Promise<void>) | void>;
|
||||||
|
|
||||||
public setConfig(config: MapCardConfig): void {
|
public setConfig(config: MapCardConfig): void {
|
||||||
if (!config) {
|
if (!config) {
|
||||||
throw new Error("Error in card configuration.");
|
throw new Error("Error in card configuration.");
|
||||||
@ -88,8 +92,6 @@ class HuiMapCard extends LitElement implements LovelaceCard {
|
|||||||
? processConfigEntities<EntityConfig>(config.entities)
|
? processConfigEntities<EntityConfig>(config.entities)
|
||||||
: []
|
: []
|
||||||
).map((entity) => entity.entity);
|
).map((entity) => entity.entity);
|
||||||
|
|
||||||
this._cleanupHistory();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public getCardSize(): number {
|
public getCardSize(): number {
|
||||||
@ -133,6 +135,9 @@ class HuiMapCard extends LitElement implements LovelaceCard {
|
|||||||
if (!this._config) {
|
if (!this._config) {
|
||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
|
if (this._error) {
|
||||||
|
return html`<div class="error">${this._error}</div>`;
|
||||||
|
}
|
||||||
return html`
|
return html`
|
||||||
<ha-card id="card" .header=${this._config.title}>
|
<ha-card id="card" .header=${this._config.title}>
|
||||||
<div id="root">
|
<div id="root">
|
||||||
@ -144,7 +149,7 @@ class HuiMapCard extends LitElement implements LovelaceCard {
|
|||||||
this._configEntities
|
this._configEntities
|
||||||
)}
|
)}
|
||||||
.zoom=${this._config.default_zoom ?? 14}
|
.zoom=${this._config.default_zoom ?? 14}
|
||||||
.paths=${this._getHistoryPaths(this._config, this._history)}
|
.paths=${this._getHistoryPaths(this._config, this._stateHistory)}
|
||||||
.autoFit=${this._config.auto_fit}
|
.autoFit=${this._config.auto_fit}
|
||||||
.darkMode=${this._config.dark_mode}
|
.darkMode=${this._config.dark_mode}
|
||||||
></ha-map>
|
></ha-map>
|
||||||
@ -176,23 +181,68 @@ class HuiMapCard extends LitElement implements LovelaceCard {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if any state has changed
|
if (changedProps.has("_stateHistory")) {
|
||||||
for (const entity of this._configEntities) {
|
return true;
|
||||||
if (oldHass.states[entity] !== this.hass!.states[entity]) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected updated(changedProps: PropertyValues): void {
|
public connectedCallback() {
|
||||||
if (this._config?.hours_to_show && this._configEntities?.length) {
|
super.connectedCallback();
|
||||||
if (changedProps.has("_config")) {
|
if (this.hasUpdated && this._configEntities?.length) {
|
||||||
this._getHistory();
|
this._subscribeHistoryTimeWindow();
|
||||||
} else if (Date.now() - this._date!.getTime() >= MINUTE) {
|
}
|
||||||
this._getHistory();
|
}
|
||||||
|
|
||||||
|
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._stateHistory = combinedHistory;
|
||||||
|
},
|
||||||
|
this._config!.hours_to_show! || DEFAULT_HOURS_TO_SHOW,
|
||||||
|
this._configEntities!,
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
).catch((err) => {
|
||||||
|
this._subscribed = undefined;
|
||||||
|
this._error = err;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _unsubscribeHistoryTimeWindow() {
|
||||||
|
if (!this._subscribed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._subscribed.then((unsubscribe) => {
|
||||||
|
if (unsubscribe) {
|
||||||
|
unsubscribe();
|
||||||
}
|
}
|
||||||
|
this._subscribed = undefined;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected updated(changedProps: PropertyValues): void {
|
||||||
|
if (this._configEntities?.length) {
|
||||||
|
if (!this._subscribed || changedProps.has("_config")) {
|
||||||
|
this._unsubscribeHistoryTimeWindow();
|
||||||
|
this._subscribeHistoryTimeWindow();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this._unsubscribeHistoryTimeWindow();
|
||||||
}
|
}
|
||||||
if (changedProps.has("_config")) {
|
if (changedProps.has("_config")) {
|
||||||
this._computePadding();
|
this._computePadding();
|
||||||
@ -272,46 +322,44 @@ class HuiMapCard extends LitElement implements LovelaceCard {
|
|||||||
private _getHistoryPaths = memoizeOne(
|
private _getHistoryPaths = memoizeOne(
|
||||||
(
|
(
|
||||||
config: MapCardConfig,
|
config: MapCardConfig,
|
||||||
history?: HassEntity[][]
|
history?: HistoryStates
|
||||||
): HaMapPaths[] | undefined => {
|
): HaMapPaths[] | undefined => {
|
||||||
if (!config.hours_to_show || !history) {
|
if (!history) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const paths: HaMapPaths[] = [];
|
const paths: HaMapPaths[] = [];
|
||||||
|
|
||||||
for (const entityStates of history) {
|
for (const entityId of Object.keys(history)) {
|
||||||
if (entityStates?.length <= 1) {
|
const entityStates = history[entityId];
|
||||||
|
if (!entityStates?.length) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// filter location data from states and remove all invalid locations
|
// filter location data from states and remove all invalid locations
|
||||||
const points = entityStates.reduce(
|
const points: HaMapPathPoint[] = [];
|
||||||
(accumulator: HaMapPathPoint[], entityState) => {
|
for (const entityState of entityStates) {
|
||||||
const latitude = entityState.attributes.latitude;
|
const latitude = entityState.a.latitude;
|
||||||
const longitude = entityState.attributes.longitude;
|
const longitude = entityState.a.longitude;
|
||||||
if (latitude && longitude) {
|
if (!latitude || !longitude) {
|
||||||
const p = {} as HaMapPathPoint;
|
continue;
|
||||||
p.point = [latitude, longitude] as LatLngTuple;
|
}
|
||||||
const t = new Date(entityState.last_updated);
|
const p = {} as HaMapPathPoint;
|
||||||
if (config.hours_to_show! > 144) {
|
p.point = [latitude, longitude] as LatLngTuple;
|
||||||
// if showing > 6 days in the history trail, show the full
|
const t = new Date(entityState.lu * 1000);
|
||||||
// date and time
|
if (config.hours_to_show! || DEFAULT_HOURS_TO_SHOW > 144) {
|
||||||
p.tooltip = formatDateTime(t, this.hass.locale);
|
// if showing > 6 days in the history trail, show the full
|
||||||
} else if (isToday(t)) {
|
// date and time
|
||||||
p.tooltip = formatTime(t, this.hass.locale);
|
p.tooltip = formatDateTime(t, this.hass.locale);
|
||||||
} else {
|
} else if (isToday(t)) {
|
||||||
p.tooltip = formatTimeWeekday(t, this.hass.locale);
|
p.tooltip = formatTime(t, this.hass.locale);
|
||||||
}
|
} else {
|
||||||
accumulator.push(p);
|
p.tooltip = formatTimeWeekday(t, this.hass.locale);
|
||||||
}
|
}
|
||||||
return accumulator;
|
points.push(p);
|
||||||
},
|
}
|
||||||
[]
|
|
||||||
) as HaMapPathPoint[];
|
|
||||||
|
|
||||||
paths.push({
|
paths.push({
|
||||||
points,
|
points,
|
||||||
color: this._getColor(entityStates[0].entity_id),
|
color: this._getColor(entityId),
|
||||||
gradualOpacity: 0.8,
|
gradualOpacity: 0.8,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -319,58 +367,6 @@ class HuiMapCard extends LitElement implements LovelaceCard {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
private async _getHistory(): Promise<void> {
|
|
||||||
this._date = new Date();
|
|
||||||
|
|
||||||
if (!this._configEntities) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const entityIds = this._configEntities!.join(",");
|
|
||||||
const endTime = new Date();
|
|
||||||
const startTime = new Date();
|
|
||||||
startTime.setHours(endTime.getHours() - this._config!.hours_to_show!);
|
|
||||||
const skipInitialState = false;
|
|
||||||
const significantChangesOnly = false;
|
|
||||||
const minimalResponse = false;
|
|
||||||
|
|
||||||
const stateHistory = await fetchRecent(
|
|
||||||
this.hass,
|
|
||||||
entityIds,
|
|
||||||
startTime,
|
|
||||||
endTime,
|
|
||||||
skipInitialState,
|
|
||||||
significantChangesOnly,
|
|
||||||
minimalResponse
|
|
||||||
);
|
|
||||||
|
|
||||||
if (stateHistory.length < 1) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this._history = stateHistory;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _cleanupHistory() {
|
|
||||||
if (!this._history) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (this._config!.hours_to_show! <= 0) {
|
|
||||||
this._history = undefined;
|
|
||||||
} else {
|
|
||||||
// remove unused entities
|
|
||||||
this._history = this._history!.reduce(
|
|
||||||
(accumulator: HassEntity[][], entityStates) => {
|
|
||||||
const entityId = entityStates[0].entity_id;
|
|
||||||
if (this._configEntities?.includes(entityId)) {
|
|
||||||
accumulator.push(entityStates);
|
|
||||||
}
|
|
||||||
return accumulator;
|
|
||||||
},
|
|
||||||
[]
|
|
||||||
) as HassEntity[][];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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