Use sensor device class for graph and precision (#18099)

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
This commit is contained in:
Paul Bottein 2023-10-23 15:01:24 +02:00 committed by GitHub
parent c6be4d6f4d
commit aeaf091b50
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 111 additions and 40 deletions

View File

@ -395,16 +395,28 @@ const processLineChartEntities = (
};
};
const stateUsesUnits = (state: HassEntity) =>
attributesHaveUnits(state.attributes);
const NUMERICAL_DOMAINS = ["counter", "input_number", "number"];
const attributesHaveUnits = (attributes: { [key: string]: any }) =>
const isNumericFromDomain = (domain: string) =>
NUMERICAL_DOMAINS.includes(domain);
const isNumericFromAttributes = (attributes: { [key: string]: any }) =>
"unit_of_measurement" in attributes || "state_class" in attributes;
const isNumericSensorEntity = (
stateObj: HassEntity,
sensorNumericalDeviceClasses: string[]
) =>
stateObj.attributes.device_class != null &&
sensorNumericalDeviceClasses.includes(stateObj.attributes.device_class);
const BLANK_UNIT = " ";
export const computeHistory = (
hass: HomeAssistant,
stateHistory: HistoryStates,
localize: LocalizeFunc
localize: LocalizeFunc,
sensorNumericalDeviceClasses: string[]
): HistoryResult => {
const lineChartDevices: { [unit: string]: HistoryStates } = {};
const timelineDevices: TimelineEntity[] = [];
@ -417,28 +429,40 @@ export const computeHistory = (
return;
}
const domain = computeDomain(entityId);
const currentState =
entityId in hass.states ? hass.states[entityId] : undefined;
const stateWithUnitorStateClass =
!currentState &&
stateInfo.find((state) => state.a && attributesHaveUnits(state.a));
const numericStateFromHistory =
currentState || isNumericFromDomain(domain)
? undefined
: stateInfo.find(
(state) => state.a && isNumericFromAttributes(state.a)
);
let unit: string | undefined;
if (currentState && stateUsesUnits(currentState)) {
unit = currentState.attributes.unit_of_measurement || " ";
} else if (stateWithUnitorStateClass) {
unit = stateWithUnitorStateClass.a.unit_of_measurement || " ";
const isNumeric =
isNumericFromDomain(domain) ||
(currentState != null &&
isNumericFromAttributes(currentState.attributes)) ||
(currentState != null &&
domain === "sensor" &&
isNumericSensorEntity(currentState, sensorNumericalDeviceClasses)) ||
numericStateFromHistory != null;
if (isNumeric) {
unit =
currentState?.attributes.unit_of_measurement ||
numericStateFromHistory?.a.unit_of_measurement ||
BLANK_UNIT;
} else {
unit = {
zone: localize("ui.dialogs.more_info_control.zone.graph_unit"),
climate: hass.config.unit_system.temperature,
counter: "#",
humidifier: "%",
input_number: "#",
number: "#",
water_heater: hass.config.unit_system.temperature,
}[computeDomain(entityId)];
}[domain];
}
if (!unit) {

View File

@ -13,3 +13,21 @@ export const getSensorDeviceClassConvertibleUnits = (
type: "sensor/device_class_convertible_units",
device_class: deviceClass,
});
export type SensorNumericDeviceClasses = {
numeric_device_classes: string[];
};
let sensorNumericDeviceClassesCache: SensorNumericDeviceClasses | undefined;
export const getSensorNumericDeviceClasses = async (
hass: HomeAssistant
): Promise<SensorNumericDeviceClasses> => {
if (sensorNumericDeviceClassesCache) {
return sensorNumericDeviceClassesCache;
}
sensorNumericDeviceClassesCache = await hass.callWS({
type: "sensor/numeric_device_classes",
});
return sensorNumericDeviceClassesCache!;
};

View File

@ -1,28 +1,29 @@
import { startOfYesterday, subHours } from "date-fns/esm";
import { css, html, LitElement, PropertyValues, nothing } from "lit";
import { customElement, property, state, query } from "lit/decorators";
import { LitElement, PropertyValues, css, html, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { isComponentLoaded } from "../../common/config/is_component_loaded";
import { fireEvent } from "../../common/dom/fire_event";
import { computeDomain } from "../../common/entity/compute_domain";
import { createSearchParam } from "../../common/url/search-params";
import { ChartResizeOptions } from "../../components/chart/ha-chart-base";
import "../../components/chart/state-history-charts";
import type { StateHistoryCharts } from "../../components/chart/state-history-charts";
import "../../components/chart/statistics-chart";
import type { StatisticsChart } from "../../components/chart/statistics-chart";
import {
computeHistory,
HistoryResult,
computeHistory,
subscribeHistoryStatesTimeWindow,
} from "../../data/history";
import {
fetchStatistics,
getStatisticMetadata,
Statistics,
StatisticsMetaData,
StatisticsTypes,
fetchStatistics,
getStatisticMetadata,
} from "../../data/recorder";
import { getSensorNumericDeviceClasses } from "../../data/sensor";
import { HomeAssistant } from "../../types";
import type { StatisticsChart } from "../../components/chart/statistics-chart";
import { ChartResizeOptions } from "../../components/chart/ha-chart-base";
declare global {
interface HASSDomEvents {
@ -213,6 +214,10 @@ export class MoreInfoHistory extends LitElement {
if (this._subscribed) {
this._unsubscribeHistory();
}
const { numeric_device_classes: sensorNumericDeviceClasses } =
await getSensorNumericDeviceClasses(this.hass);
this._subscribed = subscribeHistoryStatesTimeWindow(
this.hass!,
(combinedHistory) => {
@ -223,7 +228,8 @@ export class MoreInfoHistory extends LitElement {
this._stateHistory = computeHistory(
this.hass!,
combinedHistory,
this.hass!.localize
this.hass!.localize,
sensorNumericDeviceClasses
);
},
24,

View File

@ -1,5 +1,6 @@
import "@material/mwc-button/mwc-button";
import "@material/mwc-formfield/mwc-formfield";
import { mdiContentCopy } from "@mdi/js";
import { HassEntity } from "home-assistant-js-websocket";
import {
css,
@ -11,7 +12,6 @@ import {
} from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { mdiContentCopy } from "@mdi/js";
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
import { fireEvent } from "../../../common/dom/fire_event";
import { stopPropagation } from "../../../common/dom/stop_propagation";
@ -25,6 +25,7 @@ import {
LocalizeFunc,
LocalizeKeys,
} from "../../../common/translations/localize";
import { copyToClipboard } from "../../../common/util/copy-clipboard";
import "../../../components/ha-alert";
import "../../../components/ha-area-picker";
import "../../../components/ha-icon";
@ -38,9 +39,9 @@ import "../../../components/ha-switch";
import type { HaSwitch } from "../../../components/ha-switch";
import "../../../components/ha-textfield";
import {
CameraPreferences,
CAMERA_ORIENTATIONS,
CAMERA_SUPPORT_STREAM,
CameraPreferences,
fetchCameraPrefs,
STREAM_TYPE_HLS,
updateCameraPrefs,
@ -66,7 +67,10 @@ import {
} from "../../../data/entity_registry";
import { domainToName } from "../../../data/integration";
import { getNumberDeviceClassConvertibleUnits } from "../../../data/number";
import { getSensorDeviceClassConvertibleUnits } from "../../../data/sensor";
import {
getSensorDeviceClassConvertibleUnits,
getSensorNumericDeviceClasses,
} from "../../../data/sensor";
import {
getWeatherConvertibleUnits,
WeatherUnits,
@ -80,9 +84,8 @@ import { showVoiceAssistantsView } from "../../../dialogs/more-info/components/v
import { showMoreInfoDialog } from "../../../dialogs/more-info/show-ha-more-info-dialog";
import { haStyle } from "../../../resources/styles";
import type { HomeAssistant } from "../../../types";
import { showDeviceRegistryDetailDialog } from "../devices/device-registry-detail/show-dialog-device-registry-detail";
import { copyToClipboard } from "../../../common/util/copy-clipboard";
import { showToast } from "../../../util/toast";
import { showDeviceRegistryDetailDialog } from "../devices/device-registry-detail/show-dialog-device-registry-detail";
const OVERRIDE_DEVICE_CLASSES = {
cover: [
@ -174,6 +177,8 @@ export class EntityRegistrySettingsEditor extends LitElement {
@state() private _sensorDeviceClassConvertibleUnits?: string[];
@state() private _sensorNumericalDeviceClasses?: string[];
@state() private _weatherConvertibleUnits?: WeatherUnits;
@state() private _defaultCode?: string | null;
@ -195,8 +200,6 @@ export class EntityRegistrySettingsEditor extends LitElement {
this._name = this.entry.name || "";
this._icon = this.entry.icon || "";
this._deviceClass =
this.entry.device_class || this.entry.original_device_class;
this._origEntityId = this.entry.entity_id;
this._areaId = this.entry.area_id;
this._entityId = this.entry.entity_id;
@ -294,6 +297,14 @@ export class EntityRegistrySettingsEditor extends LitElement {
} else {
this._numberDeviceClassConvertibleUnits = [];
}
if (domain === "sensor") {
const { numeric_device_classes } = await getSensorNumericDeviceClasses(
this.hass
);
this._sensorNumericalDeviceClasses = numeric_device_classes;
} else {
this._sensorNumericalDeviceClasses = [];
}
if (domain === "sensor" && this._deviceClass) {
const { units } = await getSensorDeviceClassConvertibleUnits(
this.hass,
@ -558,7 +569,7 @@ export class EntityRegistrySettingsEditor extends LitElement {
// Allow customizing the precision for a sensor with numerical device class,
// a unit of measurement or state class
((this._deviceClass &&
!["date", "enum", "timestamp"].includes(this._deviceClass)) ||
this._sensorNumericalDeviceClasses?.includes(this._deviceClass)) ||
stateObj?.attributes.unit_of_measurement ||
stateObj?.attributes.state_class)
? html`

View File

@ -4,7 +4,7 @@ import {
HassServiceTarget,
UnsubscribeFunc,
} from "home-assistant-js-websocket/dist/types";
import { css, html, LitElement, PropertyValues } from "lit";
import { LitElement, PropertyValues, css, html } from "lit";
import { property, query, state } from "lit/decorators";
import { ensureArray } from "../../common/array/ensure-array";
import { storage } from "../../common/decorators/storage";
@ -39,10 +39,11 @@ import {
} from "../../data/device_registry";
import { subscribeEntityRegistry } from "../../data/entity_registry";
import {
computeHistory,
HistoryResult,
computeHistory,
subscribeHistory,
} from "../../data/history";
import { getSensorNumericDeviceClasses } from "../../data/sensor";
import { SubscribeMixin } from "../../mixins/subscribe-mixin";
import { haStyle } from "../../resources/styles";
import { HomeAssistant } from "../../types";
@ -306,6 +307,9 @@ class HaPanelHistory extends SubscribeMixin(LitElement) {
const now = new Date();
const { numeric_device_classes: sensorNumericDeviceClasses } =
await getSensorNumericDeviceClasses(this.hass);
this._subscribed = subscribeHistory(
this.hass,
(history) => {
@ -313,7 +317,8 @@ class HaPanelHistory extends SubscribeMixin(LitElement) {
this._stateHistory = computeHistory(
this.hass,
history,
this.hass.localize
this.hass.localize,
sensorNumericDeviceClasses
);
},
this._startDate,

View File

@ -1,22 +1,23 @@
import {
css,
CSSResultGroup,
html,
LitElement,
PropertyValues,
css,
html,
nothing,
} from "lit";
import { customElement, property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
import "../../../components/chart/state-history-charts";
import "../../../components/ha-card";
import "../../../components/ha-alert";
import "../../../components/ha-card";
import {
computeHistory,
HistoryResult,
computeHistory,
subscribeHistoryStatesTimeWindow,
} from "../../../data/history";
import { getSensorNumericDeviceClasses } from "../../../data/sensor";
import { HomeAssistant } from "../../../types";
import { hasConfigOrEntitiesChanged } from "../common/has-changed";
import { processConfigEntities } from "../common/process-config-entities";
@ -97,10 +98,14 @@ export class HuiHistoryGraphCard extends LitElement implements LovelaceCard {
this._unsubscribeHistory();
}
private _subscribeHistory() {
private async _subscribeHistory() {
if (!isComponentLoaded(this.hass!, "history") || this._subscribed) {
return;
}
const { numeric_device_classes: sensorNumericDeviceClasses } =
await getSensorNumericDeviceClasses(this.hass!);
this._subscribed = subscribeHistoryStatesTimeWindow(
this.hass!,
(combinedHistory) => {
@ -108,10 +113,12 @@ export class HuiHistoryGraphCard extends LitElement implements LovelaceCard {
// Message came in before we had a chance to unload
return;
}
this._stateHistory = computeHistory(
this.hass!,
combinedHistory,
this.hass!.localize
this.hass!.localize,
sensorNumericDeviceClasses
);
},
this._hoursToShow,