mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-19 15:26:36 +00:00
Allow customizing weather units (#12947)
Co-authored-by: Bram Kragten <mail@bramkragten.nl> Co-authored-by: foreign-sub <51928805+foreign-sub@users.noreply.github.com>
This commit is contained in:
parent
8bd7370a02
commit
7d118a5715
@ -33,6 +33,18 @@ export interface UpdateEntityRegistryEntryResult {
|
||||
require_restart?: boolean;
|
||||
}
|
||||
|
||||
export interface SensorEntityOptions {
|
||||
unit_of_measurement?: string | null;
|
||||
}
|
||||
|
||||
export interface WeatherEntityOptions {
|
||||
precipitation_unit?: string | null;
|
||||
pressure_unit?: string | null;
|
||||
temperature_unit?: string | null;
|
||||
visibility_unit?: string | null;
|
||||
wind_speed_unit?: string | null;
|
||||
}
|
||||
|
||||
export interface EntityRegistryEntryUpdateParams {
|
||||
name?: string | null;
|
||||
icon?: string | null;
|
||||
@ -42,9 +54,7 @@ export interface EntityRegistryEntryUpdateParams {
|
||||
hidden_by: string | null;
|
||||
new_entity_id?: string;
|
||||
options_domain?: string;
|
||||
options?: {
|
||||
unit_of_measurement?: string | null;
|
||||
};
|
||||
options?: SensorEntityOptions | WeatherEntityOptions;
|
||||
}
|
||||
|
||||
export const findBatteryEntity = (
|
||||
|
@ -37,14 +37,24 @@ interface ForecastAttribute {
|
||||
humidity?: number;
|
||||
condition?: string;
|
||||
daytime?: boolean;
|
||||
pressure?: number;
|
||||
wind_speed?: string;
|
||||
}
|
||||
|
||||
interface WeatherEntityAttributes extends HassEntityAttributeBase {
|
||||
temperature: number;
|
||||
attribution?: string;
|
||||
humidity?: number;
|
||||
forecast?: ForecastAttribute[];
|
||||
wind_speed: string;
|
||||
wind_bearing: string;
|
||||
pressure?: number;
|
||||
temperature?: number;
|
||||
visibility?: number;
|
||||
wind_bearing?: number | string;
|
||||
wind_speed?: number;
|
||||
precipitation_unit: string;
|
||||
pressure_unit: string;
|
||||
temperature_unit: string;
|
||||
visibility_unit: string;
|
||||
wind_speed_unit: string;
|
||||
}
|
||||
|
||||
export interface WeatherEntity extends HassEntityBase {
|
||||
@ -138,16 +148,16 @@ const cardinalDirections = [
|
||||
"N",
|
||||
];
|
||||
|
||||
const getWindBearingText = (degree: string): string => {
|
||||
const degreenum = parseInt(degree, 10);
|
||||
const getWindBearingText = (degree: number | string): string => {
|
||||
const degreenum = typeof degree === "number" ? degree : parseInt(degree, 10);
|
||||
if (isFinite(degreenum)) {
|
||||
// eslint-disable-next-line no-bitwise
|
||||
return cardinalDirections[(((degreenum + 11.25) / 22.5) | 0) % 16];
|
||||
}
|
||||
return degree;
|
||||
return typeof degree === "number" ? degree.toString() : degree;
|
||||
};
|
||||
|
||||
const getWindBearing = (bearing: string): string => {
|
||||
const getWindBearing = (bearing: number | string): string => {
|
||||
if (bearing != null) {
|
||||
return getWindBearingText(bearing);
|
||||
}
|
||||
@ -156,14 +166,19 @@ const getWindBearing = (bearing: string): string => {
|
||||
|
||||
export const getWind = (
|
||||
hass: HomeAssistant,
|
||||
speed: string,
|
||||
bearing: string
|
||||
stateObj: WeatherEntity,
|
||||
speed?: number,
|
||||
bearing?: number | string
|
||||
): string => {
|
||||
const speedText = `${formatNumber(speed, hass.locale)} ${getWeatherUnit(
|
||||
const speedText =
|
||||
speed !== undefined && speed !== null
|
||||
? `${formatNumber(speed, hass.locale)} ${getWeatherUnit(
|
||||
hass!,
|
||||
stateObj,
|
||||
"wind_speed"
|
||||
)}`;
|
||||
if (bearing !== null) {
|
||||
)}`
|
||||
: "-";
|
||||
if (bearing !== undefined && bearing !== null) {
|
||||
const cardinalDirection = getWindBearing(bearing);
|
||||
return `${speedText} (${
|
||||
hass.localize(
|
||||
@ -176,19 +191,28 @@ export const getWind = (
|
||||
|
||||
export const getWeatherUnit = (
|
||||
hass: HomeAssistant,
|
||||
stateObj: WeatherEntity,
|
||||
measure: string
|
||||
): string => {
|
||||
const lengthUnit = hass.config.unit_system.length || "";
|
||||
switch (measure) {
|
||||
case "pressure":
|
||||
return lengthUnit === "km" ? "hPa" : "inHg";
|
||||
case "wind_speed":
|
||||
return `${lengthUnit}/h`;
|
||||
case "visibility":
|
||||
case "length":
|
||||
return lengthUnit;
|
||||
return stateObj.attributes.visibility_unit || lengthUnit;
|
||||
case "precipitation":
|
||||
return lengthUnit === "km" ? "mm" : "in";
|
||||
return stateObj.attributes.precipitation_unit || lengthUnit === "km"
|
||||
? "mm"
|
||||
: "in";
|
||||
case "pressure":
|
||||
return stateObj.attributes.pressure_unit || lengthUnit === "km"
|
||||
? "hPa"
|
||||
: "inHg";
|
||||
case "temperature":
|
||||
return (
|
||||
stateObj.attributes.temperature_unit ||
|
||||
hass.config.unit_system.temperature
|
||||
);
|
||||
case "wind_speed":
|
||||
return stateObj.attributes.wind_speed_unit || `${lengthUnit}/h`;
|
||||
case "humidity":
|
||||
case "precipitation_probability":
|
||||
return "%";
|
||||
@ -233,7 +257,7 @@ export const getSecondaryWeatherAttribute = (
|
||||
`
|
||||
: hass!.localize(`ui.card.weather.attributes.${attribute}`)}
|
||||
${formatNumber(value, hass.locale, { maximumFractionDigits: 1 })}
|
||||
${getWeatherUnit(hass!, attribute)}
|
||||
${getWeatherUnit(hass!, stateObj, attribute)}
|
||||
`;
|
||||
};
|
||||
|
||||
@ -268,7 +292,7 @@ const getWeatherExtrema = (
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const unit = getWeatherUnit(hass!, "temperature");
|
||||
const unit = getWeatherUnit(hass!, stateObj, "temperature");
|
||||
|
||||
return html`
|
||||
${tempHigh ? `${formatNumber(tempHigh, hass.locale)} ${unit}` : ""}
|
||||
|
@ -5,7 +5,6 @@ import {
|
||||
mdiWaterPercent,
|
||||
mdiWeatherWindy,
|
||||
} from "@mdi/js";
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
@ -23,6 +22,7 @@ import {
|
||||
getWeatherUnit,
|
||||
getWind,
|
||||
isForecastHourly,
|
||||
WeatherEntity,
|
||||
weatherIcons,
|
||||
} from "../../../data/weather";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
@ -31,7 +31,7 @@ import { HomeAssistant } from "../../../types";
|
||||
class MoreInfoWeather extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public stateObj?: HassEntity;
|
||||
@property() public stateObj?: WeatherEntity;
|
||||
|
||||
protected shouldUpdate(changedProps: PropertyValues): boolean {
|
||||
if (changedProps.has("stateObj")) {
|
||||
@ -58,6 +58,8 @@ class MoreInfoWeather extends LitElement {
|
||||
const hourly = isForecastHourly(this.stateObj.attributes.forecast);
|
||||
|
||||
return html`
|
||||
${this._showValue(this.stateObj.attributes.temperature)
|
||||
? html`
|
||||
<div class="flex">
|
||||
<ha-svg-icon .path=${mdiThermometer}></ha-svg-icon>
|
||||
<div class="main">
|
||||
@ -65,12 +67,14 @@ class MoreInfoWeather extends LitElement {
|
||||
</div>
|
||||
<div>
|
||||
${formatNumber(
|
||||
this.stateObj.attributes.temperature,
|
||||
this.stateObj.attributes.temperature!,
|
||||
this.hass.locale
|
||||
)}
|
||||
${getWeatherUnit(this.hass, "temperature")}
|
||||
${getWeatherUnit(this.hass, this.stateObj, "temperature")}
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
${this._showValue(this.stateObj.attributes.pressure)
|
||||
? html`
|
||||
<div class="flex">
|
||||
@ -80,10 +84,10 @@ class MoreInfoWeather extends LitElement {
|
||||
</div>
|
||||
<div>
|
||||
${formatNumber(
|
||||
this.stateObj.attributes.pressure,
|
||||
this.stateObj.attributes.pressure!,
|
||||
this.hass.locale
|
||||
)}
|
||||
${getWeatherUnit(this.hass, "pressure")}
|
||||
${getWeatherUnit(this.hass, this.stateObj, "pressure")}
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
@ -97,7 +101,7 @@ class MoreInfoWeather extends LitElement {
|
||||
</div>
|
||||
<div>
|
||||
${formatNumber(
|
||||
this.stateObj.attributes.humidity,
|
||||
this.stateObj.attributes.humidity!,
|
||||
this.hass.locale
|
||||
)}
|
||||
%
|
||||
@ -115,7 +119,8 @@ class MoreInfoWeather extends LitElement {
|
||||
<div>
|
||||
${getWind(
|
||||
this.hass,
|
||||
this.stateObj.attributes.wind_speed,
|
||||
this.stateObj,
|
||||
this.stateObj.attributes.wind_speed!,
|
||||
this.stateObj.attributes.wind_bearing
|
||||
)}
|
||||
</div>
|
||||
@ -131,10 +136,10 @@ class MoreInfoWeather extends LitElement {
|
||||
</div>
|
||||
<div>
|
||||
${formatNumber(
|
||||
this.stateObj.attributes.visibility,
|
||||
this.stateObj.attributes.visibility!,
|
||||
this.hass.locale
|
||||
)}
|
||||
${getWeatherUnit(this.hass, "length")}
|
||||
${getWeatherUnit(this.hass, this.stateObj, "visibility")}
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
@ -173,16 +178,24 @@ class MoreInfoWeather extends LitElement {
|
||||
`}
|
||||
<div class="templow">
|
||||
${this._showValue(item.templow)
|
||||
? `${formatNumber(item.templow, this.hass.locale)}
|
||||
${getWeatherUnit(this.hass, "temperature")}`
|
||||
? `${formatNumber(item.templow!, this.hass.locale)}
|
||||
${getWeatherUnit(
|
||||
this.hass,
|
||||
this.stateObj!,
|
||||
"temperature"
|
||||
)}`
|
||||
: hourly
|
||||
? ""
|
||||
: "—"}
|
||||
</div>
|
||||
<div class="temp">
|
||||
${this._showValue(item.temperature)
|
||||
? `${formatNumber(item.temperature, this.hass.locale)}
|
||||
${getWeatherUnit(this.hass, "temperature")}`
|
||||
? `${formatNumber(item.temperature!, this.hass.locale)}
|
||||
${getWeatherUnit(
|
||||
this.hass,
|
||||
this.stateObj!,
|
||||
"temperature"
|
||||
)}`
|
||||
: "—"}
|
||||
</div>
|
||||
</div>`
|
||||
@ -240,7 +253,7 @@ class MoreInfoWeather extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private _showValue(item: string): boolean {
|
||||
private _showValue(item: number | string | undefined): boolean {
|
||||
return typeof item !== "undefined" && item !== null;
|
||||
}
|
||||
}
|
||||
|
@ -110,6 +110,14 @@ const OVERRIDE_SENSOR_UNITS = {
|
||||
pressure: ["hPa", "Pa", "kPa", "bar", "cbar", "mbar", "mmHg", "inHg", "psi"],
|
||||
};
|
||||
|
||||
const OVERRIDE_WEATHER_UNITS = {
|
||||
precipitation: ["mm", "in"],
|
||||
pressure: ["hPa", "mbar", "mmHg", "inHg"],
|
||||
temperature: ["°C", "°F"],
|
||||
visibility: ["km", "mi"],
|
||||
wind_speed: ["km/h", "mph", "m/s"],
|
||||
};
|
||||
|
||||
const SWITCH_AS_DOMAINS = ["cover", "fan", "light", "lock", "siren"];
|
||||
|
||||
@customElement("entity-registry-settings")
|
||||
@ -140,6 +148,16 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) {
|
||||
|
||||
@state() private _unit_of_measurement?: string | null;
|
||||
|
||||
@state() private _precipitation_unit?: string | null;
|
||||
|
||||
@state() private _pressure_unit?: string | null;
|
||||
|
||||
@state() private _temperature_unit?: string | null;
|
||||
|
||||
@state() private _visibility_unit?: string | null;
|
||||
|
||||
@state() private _wind_speed_unit?: string | null;
|
||||
|
||||
@state() private _error?: string;
|
||||
|
||||
@state() private _submitting?: boolean;
|
||||
@ -223,6 +241,16 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) {
|
||||
this._unit_of_measurement = stateObj?.attributes?.unit_of_measurement;
|
||||
}
|
||||
|
||||
if (domain === "weather") {
|
||||
const stateObj: HassEntity | undefined =
|
||||
this.hass.states[this.entry.entity_id];
|
||||
this._precipitation_unit = stateObj?.attributes?.precipitation_unit;
|
||||
this._pressure_unit = stateObj?.attributes?.pressure_unit;
|
||||
this._temperature_unit = stateObj?.attributes?.temperature_unit;
|
||||
this._visibility_unit = stateObj?.attributes?.visibility_unit;
|
||||
this._wind_speed_unit = stateObj?.attributes?.wind_speed_unit;
|
||||
}
|
||||
|
||||
const deviceClasses: string[][] = OVERRIDE_DEVICE_CLASSES[domain];
|
||||
|
||||
if (!deviceClasses) {
|
||||
@ -358,6 +386,90 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) {
|
||||
</ha-select>
|
||||
`
|
||||
: ""}
|
||||
${domain === "weather"
|
||||
? html`
|
||||
<ha-select
|
||||
.label=${this.hass.localize(
|
||||
"ui.dialogs.entity_registry.editor.precipitation_unit"
|
||||
)}
|
||||
.value=${this._precipitation_unit}
|
||||
naturalMenuWidth
|
||||
fixedMenuPosition
|
||||
@selected=${this._precipitationUnitChanged}
|
||||
@closed=${stopPropagation}
|
||||
>
|
||||
${OVERRIDE_WEATHER_UNITS.precipitation.map(
|
||||
(unit: string) => html`
|
||||
<mwc-list-item .value=${unit}>${unit}</mwc-list-item>
|
||||
`
|
||||
)}
|
||||
</ha-select>
|
||||
<ha-select
|
||||
.label=${this.hass.localize(
|
||||
"ui.dialogs.entity_registry.editor.pressure_unit"
|
||||
)}
|
||||
.value=${this._pressure_unit}
|
||||
naturalMenuWidth
|
||||
fixedMenuPosition
|
||||
@selected=${this._pressureUnitChanged}
|
||||
@closed=${stopPropagation}
|
||||
>
|
||||
${OVERRIDE_WEATHER_UNITS.pressure.map(
|
||||
(unit: string) => html`
|
||||
<mwc-list-item .value=${unit}>${unit}</mwc-list-item>
|
||||
`
|
||||
)}
|
||||
</ha-select>
|
||||
<ha-select
|
||||
.label=${this.hass.localize(
|
||||
"ui.dialogs.entity_registry.editor.temperature_unit"
|
||||
)}
|
||||
.value=${this._temperature_unit}
|
||||
naturalMenuWidth
|
||||
fixedMenuPosition
|
||||
@selected=${this._temperatureUnitChanged}
|
||||
@closed=${stopPropagation}
|
||||
>
|
||||
${OVERRIDE_WEATHER_UNITS.temperature.map(
|
||||
(unit: string) => html`
|
||||
<mwc-list-item .value=${unit}>${unit}</mwc-list-item>
|
||||
`
|
||||
)}
|
||||
</ha-select>
|
||||
<ha-select
|
||||
.label=${this.hass.localize(
|
||||
"ui.dialogs.entity_registry.editor.visibility_unit"
|
||||
)}
|
||||
.value=${this._visibility_unit}
|
||||
naturalMenuWidth
|
||||
fixedMenuPosition
|
||||
@selected=${this._visibilityUnitChanged}
|
||||
@closed=${stopPropagation}
|
||||
>
|
||||
${OVERRIDE_WEATHER_UNITS.visibility.map(
|
||||
(unit: string) => html`
|
||||
<mwc-list-item .value=${unit}>${unit}</mwc-list-item>
|
||||
`
|
||||
)}
|
||||
</ha-select>
|
||||
<ha-select
|
||||
.label=${this.hass.localize(
|
||||
"ui.dialogs.entity_registry.editor.wind_speed_unit"
|
||||
)}
|
||||
.value=${this._wind_speed_unit}
|
||||
naturalMenuWidth
|
||||
fixedMenuPosition
|
||||
@selected=${this._windSpeedUnitChanged}
|
||||
@closed=${stopPropagation}
|
||||
>
|
||||
${OVERRIDE_WEATHER_UNITS.wind_speed.map(
|
||||
(unit: string) => html`
|
||||
<mwc-list-item .value=${unit}>${unit}</mwc-list-item>
|
||||
`
|
||||
)}
|
||||
</ha-select>
|
||||
`
|
||||
: ""}
|
||||
${domain === "switch"
|
||||
? html`<ha-select
|
||||
.label=${this.hass.localize(
|
||||
@ -628,6 +740,31 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) {
|
||||
this._unit_of_measurement = ev.target.value;
|
||||
}
|
||||
|
||||
private _precipitationUnitChanged(ev): void {
|
||||
this._error = undefined;
|
||||
this._precipitation_unit = ev.target.value;
|
||||
}
|
||||
|
||||
private _pressureUnitChanged(ev): void {
|
||||
this._error = undefined;
|
||||
this._pressure_unit = ev.target.value;
|
||||
}
|
||||
|
||||
private _temperatureUnitChanged(ev): void {
|
||||
this._error = undefined;
|
||||
this._temperature_unit = ev.target.value;
|
||||
}
|
||||
|
||||
private _visibilityUnitChanged(ev): void {
|
||||
this._error = undefined;
|
||||
this._visibility_unit = ev.target.value;
|
||||
}
|
||||
|
||||
private _windSpeedUnitChanged(ev): void {
|
||||
this._error = undefined;
|
||||
this._wind_speed_unit = ev.target.value;
|
||||
}
|
||||
|
||||
private _switchAsChanged(ev): void {
|
||||
if (ev.target.value === "") {
|
||||
return;
|
||||
@ -730,6 +867,23 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) {
|
||||
params.options_domain = "sensor";
|
||||
params.options = { unit_of_measurement: this._unit_of_measurement };
|
||||
}
|
||||
if (
|
||||
domain === "weather" &&
|
||||
(stateObj?.attributes?.precipitation_unit !== this._precipitation_unit ||
|
||||
stateObj?.attributes?.pressure_unit !== this._pressure_unit ||
|
||||
stateObj?.attributes?.temperature_unit !== this._temperature_unit ||
|
||||
stateObj?.attributes?.visbility_unit !== this._visibility_unit ||
|
||||
stateObj?.attributes?.wind_speed_unit !== this._wind_speed_unit)
|
||||
) {
|
||||
params.options_domain = "weather";
|
||||
params.options = {
|
||||
precipitation_unit: this._precipitation_unit,
|
||||
pressure_unit: this._pressure_unit,
|
||||
temperature_unit: this._temperature_unit,
|
||||
visibility_unit: this._visibility_unit,
|
||||
wind_speed_unit: this._wind_speed_unit,
|
||||
};
|
||||
}
|
||||
try {
|
||||
const result = await updateEntityRegistryEntry(
|
||||
this.hass!,
|
||||
|
@ -228,12 +228,21 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
|
||||
</div>
|
||||
<div class="temp-attribute">
|
||||
<div class="temp">
|
||||
${stateObj.attributes.temperature !== undefined &&
|
||||
stateObj.attributes.temperature !== null
|
||||
? html`
|
||||
${formatNumber(
|
||||
stateObj.attributes.temperature,
|
||||
this.hass.locale
|
||||
)} <span
|
||||
>${getWeatherUnit(this.hass, "temperature")}</span
|
||||
>${getWeatherUnit(
|
||||
this.hass,
|
||||
stateObj,
|
||||
"temperature"
|
||||
)}</span
|
||||
>
|
||||
`
|
||||
: html` `}
|
||||
</div>
|
||||
<div class="attribute">
|
||||
${this._config.secondary_info_attribute !== undefined
|
||||
@ -255,6 +264,7 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
|
||||
"wind_speed"
|
||||
? getWind(
|
||||
this.hass,
|
||||
stateObj,
|
||||
stateObj.attributes.wind_speed,
|
||||
stateObj.attributes.wind_bearing
|
||||
)
|
||||
@ -267,6 +277,7 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
|
||||
)}
|
||||
${getWeatherUnit(
|
||||
this.hass,
|
||||
stateObj,
|
||||
this._config.secondary_info_attribute
|
||||
)}
|
||||
`}
|
||||
|
@ -114,7 +114,9 @@ class HuiWeatherEntityRow extends LitElement implements LovelaceRow {
|
||||
})}
|
||||
>
|
||||
<div>
|
||||
${UNAVAILABLE_STATES.includes(stateObj.state)
|
||||
${UNAVAILABLE_STATES.includes(stateObj.state) ||
|
||||
stateObj.attributes.temperature === undefined ||
|
||||
stateObj.attributes.temperature === null
|
||||
? computeStateDisplay(
|
||||
this.hass.localize,
|
||||
stateObj,
|
||||
@ -125,7 +127,7 @@ class HuiWeatherEntityRow extends LitElement implements LovelaceRow {
|
||||
stateObj.attributes.temperature,
|
||||
this.hass.locale
|
||||
)}
|
||||
${getWeatherUnit(this.hass, "temperature")}
|
||||
${getWeatherUnit(this.hass, stateObj, "temperature")}
|
||||
`}
|
||||
</div>
|
||||
<div class="secondary">
|
||||
|
@ -811,6 +811,11 @@
|
||||
"icon_error": "Icons should be in the format 'prefix:iconname', e.g. 'mdi:home'",
|
||||
"entity_id": "Entity ID",
|
||||
"unit_of_measurement": "Unit of Measurement",
|
||||
"precipitation_unit": "Precipitation unit",
|
||||
"pressure_unit": "Barometric pressure unit",
|
||||
"temperature_unit": "Temperature unit",
|
||||
"visibility_unit": "Visibility unit",
|
||||
"wind_speed_unit": "Wind speed unit",
|
||||
"device_class": "Show as",
|
||||
"device_classes": {
|
||||
"binary_sensor": {
|
||||
|
Loading…
x
Reference in New Issue
Block a user