From 7d118a57151bf64b8a85e07686cf73140d47ee3e Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Thu, 23 Jun 2022 10:48:39 +0200 Subject: [PATCH] Allow customizing weather units (#12947) Co-authored-by: Bram Kragten Co-authored-by: foreign-sub <51928805+foreign-sub@users.noreply.github.com> --- src/data/entity_registry.ts | 16 +- src/data/weather.ts | 70 +++++--- .../more-info/controls/more-info-weather.ts | 65 +++++--- .../entities/entity-registry-settings.ts | 154 ++++++++++++++++++ .../cards/hui-weather-forecast-card.ts | 23 ++- .../entity-rows/hui-weather-entity-row.ts | 6 +- src/translations/en.json | 5 + 7 files changed, 279 insertions(+), 60 deletions(-) diff --git a/src/data/entity_registry.ts b/src/data/entity_registry.ts index e77d75c2b5..4c7a931d0f 100644 --- a/src/data/entity_registry.ts +++ b/src/data/entity_registry.ts @@ -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 = ( diff --git a/src/data/weather.ts b/src/data/weather.ts index a09385b526..f77da5c9b0 100644 --- a/src/data/weather.ts +++ b/src/data/weather.ts @@ -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( - hass!, - "wind_speed" - )}`; - if (bearing !== null) { + const speedText = + speed !== undefined && speed !== null + ? `${formatNumber(speed, hass.locale)} ${getWeatherUnit( + hass!, + stateObj, + "wind_speed" + )}` + : "-"; + 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}` : ""} diff --git a/src/dialogs/more-info/controls/more-info-weather.ts b/src/dialogs/more-info/controls/more-info-weather.ts index e93e9cbfe8..a680dbd754 100644 --- a/src/dialogs/more-info/controls/more-info-weather.ts +++ b/src/dialogs/more-info/controls/more-info-weather.ts @@ -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,19 +58,23 @@ class MoreInfoWeather extends LitElement { const hourly = isForecastHourly(this.stateObj.attributes.forecast); return html` -
- -
- ${this.hass.localize("ui.card.weather.attributes.temperature")} -
-
- ${formatNumber( - this.stateObj.attributes.temperature, - this.hass.locale - )} - ${getWeatherUnit(this.hass, "temperature")} -
-
+ ${this._showValue(this.stateObj.attributes.temperature) + ? html` +
+ +
+ ${this.hass.localize("ui.card.weather.attributes.temperature")} +
+
+ ${formatNumber( + this.stateObj.attributes.temperature!, + this.hass.locale + )} + ${getWeatherUnit(this.hass, this.stateObj, "temperature")} +
+
+ ` + : ""} ${this._showValue(this.stateObj.attributes.pressure) ? html`
@@ -80,10 +84,10 @@ class MoreInfoWeather extends LitElement {
${formatNumber( - this.stateObj.attributes.pressure, + this.stateObj.attributes.pressure!, this.hass.locale )} - ${getWeatherUnit(this.hass, "pressure")} + ${getWeatherUnit(this.hass, this.stateObj, "pressure")}
` @@ -97,7 +101,7 @@ class MoreInfoWeather extends LitElement {
${formatNumber( - this.stateObj.attributes.humidity, + this.stateObj.attributes.humidity!, this.hass.locale )} % @@ -115,7 +119,8 @@ class MoreInfoWeather extends LitElement {
${getWind( this.hass, - this.stateObj.attributes.wind_speed, + this.stateObj, + this.stateObj.attributes.wind_speed!, this.stateObj.attributes.wind_bearing )}
@@ -131,10 +136,10 @@ class MoreInfoWeather extends LitElement {
${formatNumber( - this.stateObj.attributes.visibility, + this.stateObj.attributes.visibility!, this.hass.locale )} - ${getWeatherUnit(this.hass, "length")} + ${getWeatherUnit(this.hass, this.stateObj, "visibility")}
` @@ -173,16 +178,24 @@ class MoreInfoWeather extends LitElement { `}
${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 ? "" : "—"}
${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" + )}` : "—"}
` @@ -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; } } diff --git a/src/panels/config/entities/entity-registry-settings.ts b/src/panels/config/entities/entity-registry-settings.ts index f525a5468b..5ee92cc750 100644 --- a/src/panels/config/entities/entity-registry-settings.ts +++ b/src/panels/config/entities/entity-registry-settings.ts @@ -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) { ` : ""} + ${domain === "weather" + ? html` + + ${OVERRIDE_WEATHER_UNITS.precipitation.map( + (unit: string) => html` + ${unit} + ` + )} + + + ${OVERRIDE_WEATHER_UNITS.pressure.map( + (unit: string) => html` + ${unit} + ` + )} + + + ${OVERRIDE_WEATHER_UNITS.temperature.map( + (unit: string) => html` + ${unit} + ` + )} + + + ${OVERRIDE_WEATHER_UNITS.visibility.map( + (unit: string) => html` + ${unit} + ` + )} + + + ${OVERRIDE_WEATHER_UNITS.wind_speed.map( + (unit: string) => html` + ${unit} + ` + )} + + ` + : ""} ${domain === "switch" ? html`
- ${formatNumber( - stateObj.attributes.temperature, - this.hass.locale - )} ${getWeatherUnit(this.hass, "temperature")} + ${stateObj.attributes.temperature !== undefined && + stateObj.attributes.temperature !== null + ? html` + ${formatNumber( + stateObj.attributes.temperature, + this.hass.locale + )} ${getWeatherUnit( + this.hass, + stateObj, + "temperature" + )} + ` + : html` `}
${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 )} `} diff --git a/src/panels/lovelace/entity-rows/hui-weather-entity-row.ts b/src/panels/lovelace/entity-rows/hui-weather-entity-row.ts index e2e0971399..3b0d17ff37 100644 --- a/src/panels/lovelace/entity-rows/hui-weather-entity-row.ts +++ b/src/panels/lovelace/entity-rows/hui-weather-entity-row.ts @@ -114,7 +114,9 @@ class HuiWeatherEntityRow extends LitElement implements LovelaceRow { })} >
- ${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")} `}
diff --git a/src/translations/en.json b/src/translations/en.json index 059fd3b2ea..076cc9f6e0 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -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": {