mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-22 00:36:34 +00:00
Convert history calls to use new websocket endpoint (#12662)
This commit is contained in:
parent
4cfb6713cb
commit
f807618f75
@ -2,67 +2,74 @@ import { HassEntity } from "home-assistant-js-websocket";
|
|||||||
import { UNAVAILABLE, UNKNOWN } from "../../data/entity";
|
import { UNAVAILABLE, UNKNOWN } from "../../data/entity";
|
||||||
import { FrontendLocaleData } from "../../data/translation";
|
import { FrontendLocaleData } from "../../data/translation";
|
||||||
import {
|
import {
|
||||||
updateIsInstalling,
|
|
||||||
UpdateEntity,
|
|
||||||
UPDATE_SUPPORT_PROGRESS,
|
UPDATE_SUPPORT_PROGRESS,
|
||||||
|
updateIsInstallingFromAttributes,
|
||||||
} from "../../data/update";
|
} from "../../data/update";
|
||||||
import { formatDate } from "../datetime/format_date";
|
import { formatDate } from "../datetime/format_date";
|
||||||
import { formatDateTime } from "../datetime/format_date_time";
|
import { formatDateTime } from "../datetime/format_date_time";
|
||||||
import { formatTime } from "../datetime/format_time";
|
import { formatTime } from "../datetime/format_time";
|
||||||
import { formatNumber, isNumericState } from "../number/format_number";
|
import { formatNumber, isNumericFromAttributes } from "../number/format_number";
|
||||||
import { LocalizeFunc } from "../translations/localize";
|
import { LocalizeFunc } from "../translations/localize";
|
||||||
import { computeStateDomain } from "./compute_state_domain";
|
import { supportsFeatureFromAttributes } from "./supports-feature";
|
||||||
import { supportsFeature } from "./supports-feature";
|
|
||||||
import { formatDuration, UNIT_TO_SECOND_CONVERT } from "../datetime/duration";
|
import { formatDuration, UNIT_TO_SECOND_CONVERT } from "../datetime/duration";
|
||||||
|
import { computeDomain } from "./compute_domain";
|
||||||
|
|
||||||
export const computeStateDisplay = (
|
export const computeStateDisplay = (
|
||||||
localize: LocalizeFunc,
|
localize: LocalizeFunc,
|
||||||
stateObj: HassEntity,
|
stateObj: HassEntity,
|
||||||
locale: FrontendLocaleData,
|
locale: FrontendLocaleData,
|
||||||
state?: string
|
state?: string
|
||||||
): string => {
|
): string =>
|
||||||
const compareState = state !== undefined ? state : stateObj.state;
|
computeStateDisplayFromEntityAttributes(
|
||||||
|
localize,
|
||||||
|
locale,
|
||||||
|
stateObj.entity_id,
|
||||||
|
stateObj.attributes,
|
||||||
|
state !== undefined ? state : stateObj.state
|
||||||
|
);
|
||||||
|
|
||||||
if (compareState === UNKNOWN || compareState === UNAVAILABLE) {
|
export const computeStateDisplayFromEntityAttributes = (
|
||||||
return localize(`state.default.${compareState}`);
|
localize: LocalizeFunc,
|
||||||
|
locale: FrontendLocaleData,
|
||||||
|
entityId: string,
|
||||||
|
attributes: any,
|
||||||
|
state: string
|
||||||
|
): string => {
|
||||||
|
if (state === UNKNOWN || state === UNAVAILABLE) {
|
||||||
|
return localize(`state.default.${state}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Entities with a `unit_of_measurement` or `state_class` are numeric values and should use `formatNumber`
|
// Entities with a `unit_of_measurement` or `state_class` are numeric values and should use `formatNumber`
|
||||||
if (isNumericState(stateObj)) {
|
if (isNumericFromAttributes(attributes)) {
|
||||||
// state is duration
|
// state is duration
|
||||||
if (
|
if (
|
||||||
stateObj.attributes.device_class === "duration" &&
|
attributes.device_class === "duration" &&
|
||||||
stateObj.attributes.unit_of_measurement &&
|
attributes.unit_of_measurement &&
|
||||||
UNIT_TO_SECOND_CONVERT[stateObj.attributes.unit_of_measurement]
|
UNIT_TO_SECOND_CONVERT[attributes.unit_of_measurement]
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
return formatDuration(
|
return formatDuration(state, attributes.unit_of_measurement);
|
||||||
compareState,
|
|
||||||
stateObj.attributes.unit_of_measurement
|
|
||||||
);
|
|
||||||
} catch (_err) {
|
} catch (_err) {
|
||||||
// fallback to default
|
// fallback to default
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (stateObj.attributes.device_class === "monetary") {
|
if (attributes.device_class === "monetary") {
|
||||||
try {
|
try {
|
||||||
return formatNumber(compareState, locale, {
|
return formatNumber(state, locale, {
|
||||||
style: "currency",
|
style: "currency",
|
||||||
currency: stateObj.attributes.unit_of_measurement,
|
currency: attributes.unit_of_measurement,
|
||||||
minimumFractionDigits: 2,
|
minimumFractionDigits: 2,
|
||||||
});
|
});
|
||||||
} catch (_err) {
|
} catch (_err) {
|
||||||
// fallback to default
|
// fallback to default
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return `${formatNumber(compareState, locale)}${
|
return `${formatNumber(state, locale)}${
|
||||||
stateObj.attributes.unit_of_measurement
|
attributes.unit_of_measurement ? " " + attributes.unit_of_measurement : ""
|
||||||
? " " + stateObj.attributes.unit_of_measurement
|
|
||||||
: ""
|
|
||||||
}`;
|
}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const domain = computeStateDomain(stateObj);
|
const domain = computeDomain(entityId);
|
||||||
|
|
||||||
if (domain === "input_datetime") {
|
if (domain === "input_datetime") {
|
||||||
if (state !== undefined) {
|
if (state !== undefined) {
|
||||||
@ -97,36 +104,32 @@ export const computeStateDisplay = (
|
|||||||
} else {
|
} else {
|
||||||
// If not trying to display an explicit state, create `Date` object from `stateObj`'s attributes then format.
|
// If not trying to display an explicit state, create `Date` object from `stateObj`'s attributes then format.
|
||||||
let date: Date;
|
let date: Date;
|
||||||
if (stateObj.attributes.has_date && stateObj.attributes.has_time) {
|
if (attributes.has_date && attributes.has_time) {
|
||||||
date = new Date(
|
date = new Date(
|
||||||
stateObj.attributes.year,
|
attributes.year,
|
||||||
stateObj.attributes.month - 1,
|
attributes.month - 1,
|
||||||
stateObj.attributes.day,
|
attributes.day,
|
||||||
stateObj.attributes.hour,
|
attributes.hour,
|
||||||
stateObj.attributes.minute
|
attributes.minute
|
||||||
);
|
);
|
||||||
return formatDateTime(date, locale);
|
return formatDateTime(date, locale);
|
||||||
}
|
}
|
||||||
if (stateObj.attributes.has_date) {
|
if (attributes.has_date) {
|
||||||
date = new Date(
|
date = new Date(attributes.year, attributes.month - 1, attributes.day);
|
||||||
stateObj.attributes.year,
|
|
||||||
stateObj.attributes.month - 1,
|
|
||||||
stateObj.attributes.day
|
|
||||||
);
|
|
||||||
return formatDate(date, locale);
|
return formatDate(date, locale);
|
||||||
}
|
}
|
||||||
if (stateObj.attributes.has_time) {
|
if (attributes.has_time) {
|
||||||
date = new Date();
|
date = new Date();
|
||||||
date.setHours(stateObj.attributes.hour, stateObj.attributes.minute);
|
date.setHours(attributes.hour, attributes.minute);
|
||||||
return formatTime(date, locale);
|
return formatTime(date, locale);
|
||||||
}
|
}
|
||||||
return stateObj.state;
|
return state;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (domain === "humidifier") {
|
if (domain === "humidifier") {
|
||||||
if (compareState === "on" && stateObj.attributes.humidity) {
|
if (state === "on" && attributes.humidity) {
|
||||||
return `${stateObj.attributes.humidity} %`;
|
return `${attributes.humidity} %`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,7 +139,7 @@ export const computeStateDisplay = (
|
|||||||
domain === "number" ||
|
domain === "number" ||
|
||||||
domain === "input_number"
|
domain === "input_number"
|
||||||
) {
|
) {
|
||||||
return formatNumber(compareState, locale);
|
return formatNumber(state, locale);
|
||||||
}
|
}
|
||||||
|
|
||||||
// state of button is a timestamp
|
// state of button is a timestamp
|
||||||
@ -144,12 +147,12 @@ export const computeStateDisplay = (
|
|||||||
domain === "button" ||
|
domain === "button" ||
|
||||||
domain === "input_button" ||
|
domain === "input_button" ||
|
||||||
domain === "scene" ||
|
domain === "scene" ||
|
||||||
(domain === "sensor" && stateObj.attributes.device_class === "timestamp")
|
(domain === "sensor" && attributes.device_class === "timestamp")
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
return formatDateTime(new Date(compareState), locale);
|
return formatDateTime(new Date(state), locale);
|
||||||
} catch (_err) {
|
} catch (_err) {
|
||||||
return compareState;
|
return state;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -160,30 +163,28 @@ export const computeStateDisplay = (
|
|||||||
// When the latest version is skipped, show the latest version
|
// When the latest version is skipped, show the latest version
|
||||||
// When update is not available, show "Up-to-date"
|
// When update is not available, show "Up-to-date"
|
||||||
// When update is not available and there is no latest_version show "Unavailable"
|
// When update is not available and there is no latest_version show "Unavailable"
|
||||||
return compareState === "on"
|
return state === "on"
|
||||||
? updateIsInstalling(stateObj as UpdateEntity)
|
? updateIsInstallingFromAttributes(attributes)
|
||||||
? supportsFeature(stateObj, UPDATE_SUPPORT_PROGRESS)
|
? supportsFeatureFromAttributes(attributes, UPDATE_SUPPORT_PROGRESS)
|
||||||
? localize("ui.card.update.installing_with_progress", {
|
? localize("ui.card.update.installing_with_progress", {
|
||||||
progress: stateObj.attributes.in_progress,
|
progress: attributes.in_progress,
|
||||||
})
|
})
|
||||||
: localize("ui.card.update.installing")
|
: localize("ui.card.update.installing")
|
||||||
: stateObj.attributes.latest_version
|
: attributes.latest_version
|
||||||
: stateObj.attributes.skipped_version ===
|
: attributes.skipped_version === attributes.latest_version
|
||||||
stateObj.attributes.latest_version
|
? attributes.latest_version ?? localize("state.default.unavailable")
|
||||||
? stateObj.attributes.latest_version ??
|
|
||||||
localize("state.default.unavailable")
|
|
||||||
: localize("ui.card.update.up_to_date");
|
: localize("ui.card.update.up_to_date");
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
// Return device class translation
|
// Return device class translation
|
||||||
(stateObj.attributes.device_class &&
|
(attributes.device_class &&
|
||||||
localize(
|
localize(
|
||||||
`component.${domain}.state.${stateObj.attributes.device_class}.${compareState}`
|
`component.${domain}.state.${attributes.device_class}.${state}`
|
||||||
)) ||
|
)) ||
|
||||||
// Return default translation
|
// Return default translation
|
||||||
localize(`component.${domain}.state._.${compareState}`) ||
|
localize(`component.${domain}.state._.${state}`) ||
|
||||||
// We don't know! Return the raw state.
|
// We don't know! Return the raw state.
|
||||||
compareState
|
state
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,7 +1,13 @@
|
|||||||
import { HassEntity } from "home-assistant-js-websocket";
|
import { HassEntity } from "home-assistant-js-websocket";
|
||||||
import { computeObjectId } from "./compute_object_id";
|
import { computeObjectId } from "./compute_object_id";
|
||||||
|
|
||||||
|
export const computeStateNameFromEntityAttributes = (
|
||||||
|
entityId: string,
|
||||||
|
attributes: { [key: string]: any }
|
||||||
|
): string =>
|
||||||
|
attributes.friendly_name === undefined
|
||||||
|
? computeObjectId(entityId).replace(/_/g, " ")
|
||||||
|
: attributes.friendly_name || "";
|
||||||
|
|
||||||
export const computeStateName = (stateObj: HassEntity): string =>
|
export const computeStateName = (stateObj: HassEntity): string =>
|
||||||
stateObj.attributes.friendly_name === undefined
|
computeStateNameFromEntityAttributes(stateObj.entity_id, stateObj.attributes);
|
||||||
? computeObjectId(stateObj.entity_id).replace(/_/g, " ")
|
|
||||||
: stateObj.attributes.friendly_name || "";
|
|
||||||
|
@ -3,6 +3,13 @@ import { HassEntity } from "home-assistant-js-websocket";
|
|||||||
export const supportsFeature = (
|
export const supportsFeature = (
|
||||||
stateObj: HassEntity,
|
stateObj: HassEntity,
|
||||||
feature: number
|
feature: number
|
||||||
|
): boolean => supportsFeatureFromAttributes(stateObj.attributes, feature);
|
||||||
|
|
||||||
|
export const supportsFeatureFromAttributes = (
|
||||||
|
attributes: {
|
||||||
|
[key: string]: any;
|
||||||
|
},
|
||||||
|
feature: number
|
||||||
): boolean =>
|
): boolean =>
|
||||||
// eslint-disable-next-line no-bitwise
|
// eslint-disable-next-line no-bitwise
|
||||||
(stateObj.attributes.supported_features! & feature) !== 0;
|
(attributes.supported_features! & feature) !== 0;
|
||||||
|
@ -7,8 +7,11 @@ import { round } from "./round";
|
|||||||
* @param stateObj The entity state object
|
* @param stateObj The entity state object
|
||||||
*/
|
*/
|
||||||
export const isNumericState = (stateObj: HassEntity): boolean =>
|
export const isNumericState = (stateObj: HassEntity): boolean =>
|
||||||
!!stateObj.attributes.unit_of_measurement ||
|
isNumericFromAttributes(stateObj.attributes);
|
||||||
!!stateObj.attributes.state_class;
|
|
||||||
|
export const isNumericFromAttributes = (attributes: {
|
||||||
|
[key: string]: any;
|
||||||
|
}): boolean => !!attributes.unit_of_measurement || !!attributes.state_class;
|
||||||
|
|
||||||
export const numberFormatToLocale = (
|
export const numberFormatToLocale = (
|
||||||
localeOptions: FrontendLocaleData
|
localeOptions: FrontendLocaleData
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import { HassEntity } from "home-assistant-js-websocket";
|
|
||||||
import { LocalizeFunc } from "../common/translations/localize";
|
import { LocalizeFunc } from "../common/translations/localize";
|
||||||
import { HomeAssistant } from "../types";
|
import { HomeAssistant } from "../types";
|
||||||
import {
|
import {
|
||||||
computeHistory,
|
computeHistory,
|
||||||
fetchRecent,
|
HistoryStates,
|
||||||
HistoryResult,
|
HistoryResult,
|
||||||
LineChartUnit,
|
LineChartUnit,
|
||||||
TimelineEntity,
|
TimelineEntity,
|
||||||
entityIdHistoryNeedsAttributes,
|
entityIdHistoryNeedsAttributes,
|
||||||
|
fetchRecentWS,
|
||||||
} from "./history";
|
} from "./history";
|
||||||
|
|
||||||
export interface CacheConfig {
|
export interface CacheConfig {
|
||||||
@ -55,7 +55,7 @@ export const getRecent = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
const noAttributes = !entityIdHistoryNeedsAttributes(hass, entityId);
|
const noAttributes = !entityIdHistoryNeedsAttributes(hass, entityId);
|
||||||
const prom = fetchRecent(
|
const prom = fetchRecentWS(
|
||||||
hass,
|
hass,
|
||||||
entityId,
|
entityId,
|
||||||
startTime,
|
startTime,
|
||||||
@ -134,12 +134,12 @@ export const getRecentWithCache = (
|
|||||||
const noAttributes = !entityIdHistoryNeedsAttributes(hass, entityId);
|
const noAttributes = !entityIdHistoryNeedsAttributes(hass, entityId);
|
||||||
|
|
||||||
const genProm = async () => {
|
const genProm = async () => {
|
||||||
let fetchedHistory: HassEntity[][];
|
let fetchedHistory: HistoryStates;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const results = await Promise.all([
|
const results = await Promise.all([
|
||||||
curCacheProm,
|
curCacheProm,
|
||||||
fetchRecent(
|
fetchRecentWS(
|
||||||
hass,
|
hass,
|
||||||
entityId,
|
entityId,
|
||||||
toFetchStartTime,
|
toFetchStartTime,
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import { HassEntity } from "home-assistant-js-websocket";
|
import { HassEntity } from "home-assistant-js-websocket";
|
||||||
import { computeDomain } from "../common/entity/compute_domain";
|
import { computeDomain } from "../common/entity/compute_domain";
|
||||||
import { computeStateDisplay } from "../common/entity/compute_state_display";
|
import { computeStateDisplayFromEntityAttributes } from "../common/entity/compute_state_display";
|
||||||
import { computeStateDomain } from "../common/entity/compute_state_domain";
|
import { computeStateNameFromEntityAttributes } from "../common/entity/compute_state_name";
|
||||||
import { computeStateName } from "../common/entity/compute_state_name";
|
|
||||||
import { LocalizeFunc } from "../common/translations/localize";
|
import { LocalizeFunc } from "../common/translations/localize";
|
||||||
import { HomeAssistant } from "../types";
|
import { HomeAssistant } from "../types";
|
||||||
import { FrontendLocaleData } from "./translation";
|
import { FrontendLocaleData } from "./translation";
|
||||||
@ -27,7 +26,7 @@ const LINE_ATTRIBUTES_TO_KEEP = [
|
|||||||
|
|
||||||
export interface LineChartState {
|
export interface LineChartState {
|
||||||
state: string;
|
state: string;
|
||||||
last_changed: string;
|
last_changed: number;
|
||||||
attributes?: Record<string, any>;
|
attributes?: Record<string, any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,7 +46,7 @@ export interface LineChartUnit {
|
|||||||
export interface TimelineState {
|
export interface TimelineState {
|
||||||
state_localize: string;
|
state_localize: string;
|
||||||
state: string;
|
state: string;
|
||||||
last_changed: string;
|
last_changed: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TimelineEntity {
|
export interface TimelineEntity {
|
||||||
@ -141,6 +140,21 @@ export interface StatisticsValidationResults {
|
|||||||
[statisticId: string]: StatisticsValidationResult[];
|
[statisticId: string]: StatisticsValidationResult[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface HistoryStates {
|
||||||
|
[entityId: string]: EntityHistoryState[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EntityHistoryState {
|
||||||
|
/** state */
|
||||||
|
s: string;
|
||||||
|
/** attributes */
|
||||||
|
a: { [key: string]: any };
|
||||||
|
/** last_changed; if set, also applies to lu */
|
||||||
|
lc: number;
|
||||||
|
/** last_updated */
|
||||||
|
lu: number;
|
||||||
|
}
|
||||||
|
|
||||||
export const entityIdHistoryNeedsAttributes = (
|
export const entityIdHistoryNeedsAttributes = (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entityId: string
|
entityId: string
|
||||||
@ -181,6 +195,27 @@ export const fetchRecent = (
|
|||||||
return hass.callApi("GET", url);
|
return hass.callApi("GET", url);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const fetchRecentWS = (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
entityId: 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: [entityId],
|
||||||
|
});
|
||||||
|
|
||||||
export const fetchDate = (
|
export const fetchDate = (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
startTime: Date,
|
startTime: Date,
|
||||||
@ -198,6 +233,27 @@ export const fetchDate = (
|
|||||||
}`
|
}`
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const fetchDateWS = (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
startTime: Date,
|
||||||
|
endTime: Date,
|
||||||
|
entityId?: string
|
||||||
|
) => {
|
||||||
|
const params = {
|
||||||
|
type: "history/history_during_period",
|
||||||
|
start_time: startTime.toISOString(),
|
||||||
|
end_time: endTime.toISOString(),
|
||||||
|
minimal_response: true,
|
||||||
|
no_attributes: !!(
|
||||||
|
entityId && !entityIdHistoryNeedsAttributes(hass, entityId)
|
||||||
|
),
|
||||||
|
};
|
||||||
|
if (entityId) {
|
||||||
|
return hass.callWS<HistoryStates>({ ...params, entity_ids: [entityId] });
|
||||||
|
}
|
||||||
|
return hass.callWS<HistoryStates>(params);
|
||||||
|
};
|
||||||
|
|
||||||
const equalState = (obj1: LineChartState, obj2: LineChartState) =>
|
const equalState = (obj1: LineChartState, obj2: LineChartState) =>
|
||||||
obj1.state === obj2.state &&
|
obj1.state === obj2.state &&
|
||||||
// Only compare attributes if both states have an attributes object.
|
// Only compare attributes if both states have an attributes object.
|
||||||
@ -212,46 +268,47 @@ const equalState = (obj1: LineChartState, obj2: LineChartState) =>
|
|||||||
const processTimelineEntity = (
|
const processTimelineEntity = (
|
||||||
localize: LocalizeFunc,
|
localize: LocalizeFunc,
|
||||||
language: FrontendLocaleData,
|
language: FrontendLocaleData,
|
||||||
states: HassEntity[]
|
entityId: string,
|
||||||
|
states: EntityHistoryState[]
|
||||||
): TimelineEntity => {
|
): TimelineEntity => {
|
||||||
const data: TimelineState[] = [];
|
const data: TimelineState[] = [];
|
||||||
const last_element = states.length - 1;
|
const last: EntityHistoryState = states[states.length - 1];
|
||||||
|
|
||||||
for (const state of states) {
|
for (const state of states) {
|
||||||
if (data.length > 0 && state.state === data[data.length - 1].state) {
|
if (data.length > 0 && state.s === data[data.length - 1].state) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy the data from the last element as its the newest
|
|
||||||
// and is only needed to localize the data
|
|
||||||
if (!state.entity_id) {
|
|
||||||
state.attributes = states[last_element].attributes;
|
|
||||||
state.entity_id = states[last_element].entity_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
data.push({
|
data.push({
|
||||||
state_localize: computeStateDisplay(localize, state, language),
|
state_localize: computeStateDisplayFromEntityAttributes(
|
||||||
state: state.state,
|
localize,
|
||||||
last_changed: state.last_changed,
|
language,
|
||||||
|
entityId,
|
||||||
|
state.a || last.a,
|
||||||
|
state.s
|
||||||
|
),
|
||||||
|
state: state.s,
|
||||||
|
// lc (last_changed) may be omitted if its the same
|
||||||
|
// as lu (last_updated).
|
||||||
|
last_changed: (state.lc ? state.lc : state.lu) * 1000,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: computeStateName(states[0]),
|
name: computeStateNameFromEntityAttributes(entityId, states[0].a),
|
||||||
entity_id: states[0].entity_id,
|
entity_id: entityId,
|
||||||
data,
|
data,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const processLineChartEntities = (
|
const processLineChartEntities = (
|
||||||
unit,
|
unit,
|
||||||
entities: HassEntity[][]
|
entities: HistoryStates
|
||||||
): LineChartUnit => {
|
): LineChartUnit => {
|
||||||
const data: LineChartEntity[] = [];
|
const data: LineChartEntity[] = [];
|
||||||
|
|
||||||
for (const states of entities) {
|
Object.keys(entities).forEach((entityId) => {
|
||||||
const last: HassEntity = states[states.length - 1];
|
const states = entities[entityId];
|
||||||
const domain = computeStateDomain(last);
|
const last: EntityHistoryState = states[states.length - 1];
|
||||||
|
const domain = computeDomain(entityId);
|
||||||
const processedStates: LineChartState[] = [];
|
const processedStates: LineChartState[] = [];
|
||||||
|
|
||||||
for (const state of states) {
|
for (const state of states) {
|
||||||
@ -259,18 +316,24 @@ const processLineChartEntities = (
|
|||||||
|
|
||||||
if (DOMAINS_USE_LAST_UPDATED.includes(domain)) {
|
if (DOMAINS_USE_LAST_UPDATED.includes(domain)) {
|
||||||
processedState = {
|
processedState = {
|
||||||
state: state.state,
|
state: state.s,
|
||||||
last_changed: state.last_updated,
|
last_changed: state.lu * 1000,
|
||||||
attributes: {},
|
attributes: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const attr of LINE_ATTRIBUTES_TO_KEEP) {
|
for (const attr of LINE_ATTRIBUTES_TO_KEEP) {
|
||||||
if (attr in state.attributes) {
|
if (attr in state.a) {
|
||||||
processedState.attributes![attr] = state.attributes[attr];
|
processedState.attributes![attr] = state.a[attr];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
processedState = state;
|
processedState = {
|
||||||
|
state: state.s,
|
||||||
|
// lc (last_changed) may be omitted if its the same
|
||||||
|
// as lu (last_updated).
|
||||||
|
last_changed: (state.lc ? state.lc : state.lu) * 1000,
|
||||||
|
attributes: {},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@ -289,52 +352,53 @@ const processLineChartEntities = (
|
|||||||
|
|
||||||
data.push({
|
data.push({
|
||||||
domain,
|
domain,
|
||||||
name: computeStateName(last),
|
name: computeStateNameFromEntityAttributes(entityId, last.a),
|
||||||
entity_id: last.entity_id,
|
entity_id: entityId,
|
||||||
states: processedStates,
|
states: processedStates,
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
unit,
|
unit,
|
||||||
identifier: entities.map((states) => states[0].entity_id).join(""),
|
identifier: Object.keys(entities).join(""),
|
||||||
data,
|
data,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const stateUsesUnits = (state: HassEntity) =>
|
const stateUsesUnits = (state: HassEntity) =>
|
||||||
"unit_of_measurement" in state.attributes ||
|
attributesHaveUnits(state.attributes);
|
||||||
"state_class" in state.attributes;
|
|
||||||
|
const attributesHaveUnits = (attributes: { [key: string]: any }) =>
|
||||||
|
"unit_of_measurement" in attributes || "state_class" in attributes;
|
||||||
|
|
||||||
export const computeHistory = (
|
export const computeHistory = (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
stateHistory: HassEntity[][],
|
stateHistory: HistoryStates,
|
||||||
localize: LocalizeFunc
|
localize: LocalizeFunc
|
||||||
): HistoryResult => {
|
): HistoryResult => {
|
||||||
const lineChartDevices: { [unit: string]: HassEntity[][] } = {};
|
const lineChartDevices: { [unit: string]: HistoryStates } = {};
|
||||||
const timelineDevices: TimelineEntity[] = [];
|
const timelineDevices: TimelineEntity[] = [];
|
||||||
if (!stateHistory) {
|
if (!stateHistory) {
|
||||||
return { line: [], timeline: [] };
|
return { line: [], timeline: [] };
|
||||||
}
|
}
|
||||||
|
Object.keys(stateHistory).forEach((entityId) => {
|
||||||
stateHistory.forEach((stateInfo) => {
|
const stateInfo = stateHistory[entityId];
|
||||||
if (stateInfo.length === 0) {
|
if (stateInfo.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const entityId = stateInfo[0].entity_id;
|
|
||||||
const currentState =
|
const currentState =
|
||||||
entityId in hass.states ? hass.states[entityId] : undefined;
|
entityId in hass.states ? hass.states[entityId] : undefined;
|
||||||
const stateWithUnitorStateClass =
|
const stateWithUnitorStateClass =
|
||||||
!currentState &&
|
!currentState &&
|
||||||
stateInfo.find((state) => state.attributes && stateUsesUnits(state));
|
stateInfo.find((state) => state.a && attributesHaveUnits(state.a));
|
||||||
|
|
||||||
let unit: string | undefined;
|
let unit: string | undefined;
|
||||||
|
|
||||||
if (currentState && stateUsesUnits(currentState)) {
|
if (currentState && stateUsesUnits(currentState)) {
|
||||||
unit = currentState.attributes.unit_of_measurement || " ";
|
unit = currentState.attributes.unit_of_measurement || " ";
|
||||||
} else if (stateWithUnitorStateClass) {
|
} else if (stateWithUnitorStateClass) {
|
||||||
unit = stateWithUnitorStateClass.attributes.unit_of_measurement || " ";
|
unit = stateWithUnitorStateClass.a.unit_of_measurement || " ";
|
||||||
} else {
|
} else {
|
||||||
unit = {
|
unit = {
|
||||||
climate: hass.config.unit_system.temperature,
|
climate: hass.config.unit_system.temperature,
|
||||||
@ -348,12 +412,15 @@ export const computeHistory = (
|
|||||||
|
|
||||||
if (!unit) {
|
if (!unit) {
|
||||||
timelineDevices.push(
|
timelineDevices.push(
|
||||||
processTimelineEntity(localize, hass.locale, stateInfo)
|
processTimelineEntity(localize, hass.locale, entityId, stateInfo)
|
||||||
);
|
);
|
||||||
} else if (unit in lineChartDevices) {
|
} else if (unit in lineChartDevices && entityId in lineChartDevices[unit]) {
|
||||||
lineChartDevices[unit].push(stateInfo);
|
lineChartDevices[unit][entityId].push(...stateInfo);
|
||||||
} else {
|
} else {
|
||||||
lineChartDevices[unit] = [stateInfo];
|
if (!(unit in lineChartDevices)) {
|
||||||
|
lineChartDevices[unit] = {};
|
||||||
|
}
|
||||||
|
lineChartDevices[unit][entityId] = stateInfo;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -7,7 +7,10 @@ import type {
|
|||||||
import { BINARY_STATE_ON } from "../common/const";
|
import { BINARY_STATE_ON } from "../common/const";
|
||||||
import { computeDomain } from "../common/entity/compute_domain";
|
import { computeDomain } from "../common/entity/compute_domain";
|
||||||
import { computeStateDomain } from "../common/entity/compute_state_domain";
|
import { computeStateDomain } from "../common/entity/compute_state_domain";
|
||||||
import { supportsFeature } from "../common/entity/supports-feature";
|
import {
|
||||||
|
supportsFeature,
|
||||||
|
supportsFeatureFromAttributes,
|
||||||
|
} from "../common/entity/supports-feature";
|
||||||
import { caseInsensitiveStringCompare } from "../common/string/compare";
|
import { caseInsensitiveStringCompare } from "../common/string/compare";
|
||||||
import { showAlertDialog } from "../dialogs/generic/show-dialog-box";
|
import { showAlertDialog } from "../dialogs/generic/show-dialog-box";
|
||||||
import { HomeAssistant } from "../types";
|
import { HomeAssistant } from "../types";
|
||||||
@ -35,8 +38,13 @@ export interface UpdateEntity extends HassEntityBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const updateUsesProgress = (entity: UpdateEntity): boolean =>
|
export const updateUsesProgress = (entity: UpdateEntity): boolean =>
|
||||||
supportsFeature(entity, UPDATE_SUPPORT_PROGRESS) &&
|
updateUsesProgressFromAttributes(entity.attributes);
|
||||||
typeof entity.attributes.in_progress === "number";
|
|
||||||
|
export const updateUsesProgressFromAttributes = (attributes: {
|
||||||
|
[key: string]: any;
|
||||||
|
}): boolean =>
|
||||||
|
supportsFeatureFromAttributes(attributes, UPDATE_SUPPORT_PROGRESS) &&
|
||||||
|
typeof attributes.in_progress === "number";
|
||||||
|
|
||||||
export const updateCanInstall = (
|
export const updateCanInstall = (
|
||||||
entity: UpdateEntity,
|
entity: UpdateEntity,
|
||||||
@ -49,6 +57,11 @@ export const updateCanInstall = (
|
|||||||
export const updateIsInstalling = (entity: UpdateEntity): boolean =>
|
export const updateIsInstalling = (entity: UpdateEntity): boolean =>
|
||||||
updateUsesProgress(entity) || !!entity.attributes.in_progress;
|
updateUsesProgress(entity) || !!entity.attributes.in_progress;
|
||||||
|
|
||||||
|
export const updateIsInstallingFromAttributes = (attributes: {
|
||||||
|
[key: string]: any;
|
||||||
|
}): boolean =>
|
||||||
|
updateUsesProgressFromAttributes(attributes) || !!attributes.in_progress;
|
||||||
|
|
||||||
export const updateReleaseNotes = (hass: HomeAssistant, entityId: string) =>
|
export const updateReleaseNotes = (hass: HomeAssistant, entityId: string) =>
|
||||||
hass.callWS<string | null>({
|
hass.callWS<string | null>({
|
||||||
type: "update/release_notes",
|
type: "update/release_notes",
|
||||||
|
@ -25,7 +25,7 @@ import "../../components/ha-date-range-picker";
|
|||||||
import type { DateRangePickerRanges } from "../../components/ha-date-range-picker";
|
import type { DateRangePickerRanges } from "../../components/ha-date-range-picker";
|
||||||
import "../../components/ha-icon-button";
|
import "../../components/ha-icon-button";
|
||||||
import "../../components/ha-menu-button";
|
import "../../components/ha-menu-button";
|
||||||
import { computeHistory, fetchDate } from "../../data/history";
|
import { computeHistory, fetchDateWS } from "../../data/history";
|
||||||
import "../../layouts/ha-app-layout";
|
import "../../layouts/ha-app-layout";
|
||||||
import { haStyle } from "../../resources/styles";
|
import { haStyle } from "../../resources/styles";
|
||||||
import { HomeAssistant } from "../../types";
|
import { HomeAssistant } from "../../types";
|
||||||
@ -177,7 +177,7 @@ class HaPanelHistory extends LitElement {
|
|||||||
|
|
||||||
private async _getHistory() {
|
private async _getHistory() {
|
||||||
this._isLoading = true;
|
this._isLoading = true;
|
||||||
const dateHistory = await fetchDate(
|
const dateHistory = await fetchDateWS(
|
||||||
this.hass,
|
this.hass,
|
||||||
this._startDate,
|
this._startDate,
|
||||||
this._endDate,
|
this._endDate,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user