diff --git a/src/data/weather.ts b/src/data/weather.ts index 619240354d..d18c41ccab 100644 --- a/src/data/weather.ts +++ b/src/data/weather.ts @@ -24,10 +24,19 @@ import { } from "home-assistant-js-websocket"; import { css, html, svg, SVGTemplateResult, TemplateResult } from "lit"; import { styleMap } from "lit/directives/style-map"; +import { supportsFeature } from "../common/entity/supports-feature"; import { formatNumber } from "../common/number/format_number"; import "../components/ha-svg-icon"; import type { HomeAssistant } from "../types"; +export const enum WeatherEntityFeature { + FORECAST_DAILY = 1, + FORECAST_HOURLY = 2, + FORECAST_TWICE_DAILY = 4, +} + +export type ForecastType = "legacy" | "hourly" | "daily" | "twice_daily"; + interface ForecastAttribute { temperature: number; datetime: string; @@ -36,7 +45,7 @@ interface ForecastAttribute { precipitation_probability?: number; humidity?: number; condition?: string; - daytime?: boolean; + is_daytime?: boolean; pressure?: number; wind_speed?: string; } @@ -45,6 +54,7 @@ interface WeatherEntityAttributes extends HassEntityAttributeBase { attribution?: string; humidity?: number; forecast?: ForecastAttribute[]; + is_daytime?: boolean; pressure?: number; temperature?: number; visibility?: number; @@ -57,6 +67,11 @@ interface WeatherEntityAttributes extends HassEntityAttributeBase { wind_speed_unit: string; } +export interface ForecastEvent { + type: "hourly" | "daily" | "twice_daily"; + forecast: [ForecastAttribute] | null; +} + export interface WeatherEntity extends HassEntityBase { attributes: WeatherEntityAttributes; } @@ -225,9 +240,10 @@ export const getWeatherUnit = ( export const getSecondaryWeatherAttribute = ( hass: HomeAssistant, - stateObj: WeatherEntity + stateObj: WeatherEntity, + forecast: ForecastAttribute[] ): TemplateResult | undefined => { - const extrema = getWeatherExtrema(hass, stateObj); + const extrema = getWeatherExtrema(hass, stateObj, forecast); if (extrema) { return extrema; @@ -237,11 +253,11 @@ export const getSecondaryWeatherAttribute = ( let attribute: string; if ( - stateObj.attributes.forecast?.length && - stateObj.attributes.forecast[0].precipitation !== undefined && - stateObj.attributes.forecast[0].precipitation !== null + forecast?.length && + forecast[0].precipitation !== undefined && + forecast[0].precipitation !== null ) { - value = stateObj.attributes.forecast[0].precipitation!; + value = forecast[0].precipitation!; attribute = "precipitation"; } else if ("humidity" in stateObj.attributes) { value = stateObj.attributes.humidity!; @@ -265,9 +281,10 @@ export const getSecondaryWeatherAttribute = ( const getWeatherExtrema = ( hass: HomeAssistant, - stateObj: WeatherEntity + stateObj: WeatherEntity, + forecast: ForecastAttribute[] ): TemplateResult | undefined => { - if (!stateObj.attributes.forecast?.length) { + if (!forecast?.length) { return undefined; } @@ -275,18 +292,18 @@ const getWeatherExtrema = ( let tempHigh: number | undefined; const today = new Date().getDate(); - for (const forecast of stateObj.attributes.forecast!) { - if (new Date(forecast.datetime).getDate() !== today) { + for (const fc of forecast!) { + if (new Date(fc.datetime).getDate() !== today) { break; } - if (!tempHigh || forecast.temperature > tempHigh) { - tempHigh = forecast.temperature; + if (!tempHigh || fc.temperature > tempHigh) { + tempHigh = fc.temperature; } - if (!tempLow || (forecast.templow && forecast.templow < tempLow)) { - tempLow = forecast.templow; + if (!tempLow || (fc.templow && fc.templow < tempLow)) { + tempLow = fc.templow; } - if (!forecast.templow && (!tempLow || forecast.temperature < tempLow)) { - tempLow = forecast.temperature; + if (!fc.templow && (!tempLow || fc.temperature < tempLow)) { + tempLow = fc.temperature; } } @@ -510,7 +527,7 @@ export const weatherIcon = (state?: string, nightTime?: boolean): string => const DAY_IN_MILLISECONDS = 86400000; -export const isForecastHourly = ( +const isForecastHourly = ( forecast?: ForecastAttribute[] ): boolean | undefined => { if (forecast && forecast?.length && forecast?.length > 2) { @@ -538,3 +555,93 @@ export const getWeatherConvertibleUnits = ( hass.callWS({ type: "weather/convertible_units", }); + +const getLegacyForecast = ( + weather_attributes?: WeatherEntityAttributes | undefined +): + | { + forecast: ForecastAttribute[]; + type: "daily" | "hourly" | "twice_daily"; + } + | undefined => { + if (weather_attributes?.forecast && weather_attributes.forecast.length > 2) { + const hourly = isForecastHourly(weather_attributes.forecast); + if (hourly === true) { + const dateFirst = new Date(weather_attributes.forecast![0].datetime); + const datelast = new Date( + weather_attributes.forecast![ + weather_attributes.forecast!.length - 1 + ].datetime + ); + const dayDiff = datelast.getTime() - dateFirst.getTime(); + const dayNight = dayDiff > DAY_IN_MILLISECONDS; + return { + forecast: weather_attributes.forecast, + type: dayNight ? "twice_daily" : "hourly", + }; + } + return { forecast: weather_attributes.forecast, type: "daily" }; + } + return undefined; +}; + +export const getForecast = ( + weather_attributes?: WeatherEntityAttributes | undefined, + forecast_event?: ForecastEvent, + forecast_type?: ForecastType | undefined +): + | { + forecast: ForecastAttribute[]; + type: "daily" | "hourly" | "twice_daily"; + } + | undefined => { + if (forecast_type === undefined) { + if ( + forecast_event?.type !== undefined && + forecast_event?.forecast && + forecast_event?.forecast?.length > 2 + ) { + return { forecast: forecast_event.forecast, type: forecast_event?.type }; + } + return getLegacyForecast(weather_attributes); + } + + if (forecast_type === "legacy") { + return getLegacyForecast(weather_attributes); + } + + if ( + forecast_type === forecast_event?.type && + forecast_event?.forecast && + forecast_event?.forecast?.length > 2 + ) { + return { forecast: forecast_event.forecast, type: forecast_type }; + } + + return undefined; +}; + +export const subscribeForecast = ( + hass: HomeAssistant, + entity_id: string, + forecast_type: "daily" | "hourly" | "twice_daily", + callback: (forecastevent: ForecastEvent) => void +) => + hass.connection.subscribeMessage(callback, { + type: "weather/subscribe_forecast", + forecast_type, + entity_id, + }); + +export const getDefaultForecastType = (stateObj: HassEntityBase) => { + if (supportsFeature(stateObj, WeatherEntityFeature.FORECAST_DAILY)) { + return "daily"; + } + if (supportsFeature(stateObj, WeatherEntityFeature.FORECAST_HOURLY)) { + return "hourly"; + } + if (supportsFeature(stateObj, WeatherEntityFeature.FORECAST_TWICE_DAILY)) { + return "twice_daily"; + } + return undefined; +}; diff --git a/src/dialogs/more-info/controls/more-info-weather.ts b/src/dialogs/more-info/controls/more-info-weather.ts index 628886339d..f76e2517f1 100644 --- a/src/dialogs/more-info/controls/more-info-weather.ts +++ b/src/dialogs/more-info/controls/more-info-weather.ts @@ -13,15 +13,18 @@ import { PropertyValues, nothing, } from "lit"; -import { customElement, property } from "lit/decorators"; +import { customElement, property, state } from "lit/decorators"; import { formatDateWeekdayDay } from "../../../common/datetime/format_date"; import { formatTimeWeekday } from "../../../common/datetime/format_time"; import { formatNumber } from "../../../common/number/format_number"; import "../../../components/ha-svg-icon"; import { + getDefaultForecastType, + getForecast, getWeatherUnit, getWind, - isForecastHourly, + subscribeForecast, + ForecastEvent, WeatherEntity, weatherIcons, } from "../../../data/weather"; @@ -33,6 +36,48 @@ class MoreInfoWeather extends LitElement { @property() public stateObj?: WeatherEntity; + @state() private _forecastEvent?: ForecastEvent; + + @state() private _subscribed?: Promise<() => void>; + + private _unsubscribeForecastEvents() { + if (this._subscribed) { + this._subscribed.then((unsub) => unsub()); + this._subscribed = undefined; + } + } + + private async _subscribeForecastEvents() { + this._unsubscribeForecastEvents(); + if (!this.isConnected || !this.hass || !this.stateObj) { + return; + } + + const forecastType = getDefaultForecastType(this.stateObj); + if (forecastType) { + this._subscribed = subscribeForecast( + this.hass!, + this.stateObj!.entity_id, + forecastType, + (event) => { + this._forecastEvent = event; + } + ); + } + } + + public connectedCallback() { + super.connectedCallback(); + if (this.hasUpdated) { + this._subscribeForecastEvents(); + } + } + + public disconnectedCallback(): void { + super.disconnectedCallback(); + this._unsubscribeForecastEvents(); + } + protected shouldUpdate(changedProps: PropertyValues): boolean { if (changedProps.has("stateObj")) { return true; @@ -50,12 +95,33 @@ class MoreInfoWeather extends LitElement { return false; } + protected updated(changedProps: PropertyValues): void { + super.updated(changedProps); + + if (changedProps.has("stateObj") || !this._subscribed) { + const oldState = changedProps.get("stateObj") as + | WeatherEntity + | undefined; + if ( + oldState?.entity_id !== this.stateObj?.entity_id || + !this._subscribed + ) { + this._subscribeForecastEvents(); + } + } + } + protected render() { if (!this.hass || !this.stateObj) { return nothing; } - const hourly = isForecastHourly(this.stateObj.attributes.forecast); + const forecastData = getForecast( + this.stateObj.attributes, + this._forecastEvent + ); + const forecast = forecastData?.forecast; + const hourly = forecastData?.type === "hourly"; return html` ${this._showValue(this.stateObj.attributes.temperature) @@ -144,12 +210,12 @@ class MoreInfoWeather extends LitElement { ` : ""} - ${this.stateObj.attributes.forecast + ${forecast ? html`
${this.hass.localize("ui.card.weather.forecast")}:
- ${this.stateObj.attributes.forecast.map((item) => + ${forecast.map((item) => this._showValue(item.templow) || this._showValue(item.temperature) ? html`
${item.condition @@ -176,6 +242,9 @@ class MoreInfoWeather extends LitElement { this.hass.locale, this.hass.config )} + ${item.is_daytime !== false + ? this.hass!.localize("ui.card.weather.day") + : this.hass!.localize("ui.card.weather.night")}
`}
diff --git a/src/panels/lovelace/cards/hui-weather-forecast-card.ts b/src/panels/lovelace/cards/hui-weather-forecast-card.ts index 85f29fc0a4..b5ee99fc86 100644 --- a/src/panels/lovelace/cards/hui-weather-forecast-card.ts +++ b/src/panels/lovelace/cards/hui-weather-forecast-card.ts @@ -20,11 +20,13 @@ import "../../../components/ha-svg-icon"; import { UNAVAILABLE } from "../../../data/entity"; import { ActionHandlerEvent } from "../../../data/lovelace"; import { + getForecast, getSecondaryWeatherAttribute, getWeatherStateIcon, getWeatherUnit, getWind, - isForecastHourly, + subscribeForecast, + ForecastEvent, weatherAttrIcons, WeatherEntity, weatherSVGStyles, @@ -41,8 +43,6 @@ import type { LovelaceCard, LovelaceCardEditor } from "../types"; import type { WeatherForecastCardConfig } from "./types"; import { formatDateWeekdayShort } from "../../../common/datetime/format_date"; -const DAY_IN_MILLISECONDS = 86400000; - @customElement("hui-weather-forecast-card") class HuiWeatherForecastCard extends LitElement implements LovelaceCard { public static async getConfigElement(): Promise { @@ -72,13 +72,54 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard { @state() private _config?: WeatherForecastCardConfig; + @state() private _forecastEvent?: ForecastEvent; + + @state() private _subscribed?: Promise<() => void>; + @property({ type: Boolean, reflect: true, attribute: "veryverynarrow" }) private _veryVeryNarrow = false; private _resizeObserver?: ResizeObserver; + private _needForecastSubscription() { + return ( + this._config!.forecast_type && this._config!.forecast_type !== "legacy" + ); + } + + private _unsubscribeForecastEvents() { + if (this._subscribed) { + this._subscribed.then((unsub) => unsub()); + this._subscribed = undefined; + } + } + + private async _subscribeForecastEvents() { + this._unsubscribeForecastEvents(); + if ( + !this.isConnected || + !this.hass || + !this._config || + !this._needForecastSubscription() + ) { + return; + } + + this._subscribed = subscribeForecast( + this.hass!, + this._config!.entity, + this._config!.forecast_type as "daily" | "hourly" | "twice_daily", + (event) => { + this._forecastEvent = event; + } + ); + } + public connectedCallback(): void { super.connectedCallback(); + if (this.hasUpdated && this._config && this.hass) { + this._subscribeForecastEvents(); + } this.updateComplete.then(() => this._attachObserver()); } @@ -86,6 +127,7 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard { if (this._resizeObserver) { this._resizeObserver.disconnect(); } + this._unsubscribeForecastEvents(); } public getCardSize(): number { @@ -111,7 +153,10 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard { } protected shouldUpdate(changedProps: PropertyValues): boolean { - return hasConfigOrEntityChanged(this, changedProps); + return ( + hasConfigOrEntityChanged(this, changedProps) || + changedProps.has("forecastEvent") + ); } public willUpdate(): void { @@ -130,6 +175,10 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard { return; } + if (changedProps.has("_config") || !this._subscribed) { + this._subscribeForecastEvents(); + } + const oldHass = changedProps.get("hass") as HomeAssistant | undefined; const oldConfig = changedProps.get("_config") as | WeatherForecastCardConfig @@ -172,23 +221,19 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard { `; } + const forecastData = getForecast( + stateObj.attributes, + this._forecastEvent, + this._config?.forecast_type + ); const forecast = - this._config?.show_forecast !== false && - stateObj.attributes.forecast?.length - ? stateObj.attributes.forecast.slice(0, this._veryVeryNarrow ? 3 : 5) + this._config?.show_forecast !== false && forecastData?.forecast?.length + ? forecastData.forecast.slice(0, this._veryVeryNarrow ? 3 : 5) : undefined; const weather = !forecast || this._config?.show_current !== false; - const hourly = isForecastHourly(forecast); - let dayNight: boolean | undefined; - - if (hourly) { - const dateFirst = new Date(forecast![0].datetime); - const datelast = new Date(forecast![forecast!.length - 1].datetime); - const dayDiff = datelast.getTime() - dateFirst.getTime(); - - dayNight = dayDiff > DAY_IN_MILLISECONDS; - } + const hourly = forecastData?.type === "hourly"; + const dayNight = forecastData?.type === "twice_daily"; const weatherStateIcon = getWeatherStateIcon(stateObj.state, this); const name = this._config.name ?? computeStateName(stateObj); @@ -285,7 +330,11 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard { )} `} ` - : getSecondaryWeatherAttribute(this.hass, stateObj)} + : getSecondaryWeatherAttribute( + this.hass, + stateObj, + forecast! + )}
@@ -308,7 +357,7 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard { { weekday: "short" } )}
- ${item.daytime === undefined || item.daytime + ${item.is_daytime !== false ? this.hass!.localize( "ui.card.weather.day" ) @@ -340,7 +389,8 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard { item.condition!, this, !( - item.daytime || item.daytime === undefined + item.is_daytime || + item.is_daytime === undefined ) )}
diff --git a/src/panels/lovelace/cards/types.ts b/src/panels/lovelace/cards/types.ts index 082b6c4b53..0a7c8ee412 100644 --- a/src/panels/lovelace/cards/types.ts +++ b/src/panels/lovelace/cards/types.ts @@ -12,6 +12,7 @@ import { import { LovelaceHeaderFooterConfig } from "../header-footer/types"; import { HaDurationData } from "../../../components/ha-duration-input"; import { LovelaceTileFeatureConfig } from "../tile-features/types"; +import { ForecastType } from "../../../data/weather"; export interface AlarmPanelCardConfig extends LovelaceCardConfig { entity: string; @@ -444,6 +445,7 @@ export interface WeatherForecastCardConfig extends LovelaceCardConfig { name?: string; show_current?: boolean; show_forecast?: boolean; + forecast_type?: ForecastType; secondary_info_attribute?: keyof TranslationDict["ui"]["card"]["weather"]["attributes"]; theme?: string; tap_action?: ActionConfig; diff --git a/src/panels/lovelace/editor/config-elements/hui-weather-forecast-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-weather-forecast-card-editor.ts index 2be07c183b..a758959aeb 100644 --- a/src/panels/lovelace/editor/config-elements/hui-weather-forecast-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-weather-forecast-card-editor.ts @@ -7,12 +7,14 @@ import type { LocalizeFunc } from "../../../../common/translations/localize"; import "../../../../components/ha-form/ha-form"; import type { SchemaUnion } from "../../../../components/ha-form/types"; import { UNAVAILABLE } from "../../../../data/entity"; -import type { WeatherEntity } from "../../../../data/weather"; +import type { ForecastType, WeatherEntity } from "../../../../data/weather"; +import { WeatherEntityFeature } from "../../../../data/weather"; import type { HomeAssistant } from "../../../../types"; import type { WeatherForecastCardConfig } from "../../cards/types"; import type { LovelaceCardEditor } from "../../types"; import { actionConfigStruct } from "../structs/action-struct"; import { baseLovelaceCardConfig } from "../structs/base-card-struct"; +import { supportsFeature } from "../../../../common/entity/supports-feature"; const cardConfigStruct = assign( baseLovelaceCardConfig, @@ -22,6 +24,7 @@ const cardConfigStruct = assign( theme: optional(string()), show_current: optional(boolean()), show_forecast: optional(boolean()), + forecast_type: optional(string()), secondary_info_attribute: optional(string()), tap_action: optional(actionConfigStruct), hold_action: optional(actionConfigStruct), @@ -44,7 +47,7 @@ export class HuiWeatherForecastCardEditor if ( /* cannot show forecast in case it is unavailable on the entity */ - (config.show_forecast === true && this._has_forecast === false) || + (config.show_forecast === true && this._hasForecast === false) || /* cannot hide both weather and forecast, need one of them */ (config.show_current === false && config.show_forecast === false) ) { @@ -53,20 +56,72 @@ export class HuiWeatherForecastCardEditor config: { ...config, show_current: true, show_forecast: false }, }); } + if ( + !config.forecast_type || + !this._forecastSupported(config.forecast_type) + ) { + let forecastType: string | undefined; + if (this._forecastSupported("daily")) { + forecastType = "daily"; + } else if (this._forecastSupported("hourly")) { + forecastType = "hourly"; + } else if (this._forecastSupported("twice_daily")) { + forecastType = "twice_daily"; + } else if (this._forecastSupported("legacy")) { + forecastType = "legacy"; + } + fireEvent(this, "config-changed", { + config: { ...config, forecast_type: forecastType }, + }); + } } - get _has_forecast(): boolean | undefined { + private get _stateObj(): WeatherEntity | undefined { if (this.hass && this._config) { - const stateObj = this.hass.states[this._config.entity] as WeatherEntity; - if (stateObj && stateObj.state !== UNAVAILABLE) { - return !!stateObj.attributes.forecast?.length; - } + return this.hass.states[this._config.entity] as WeatherEntity; } return undefined; } + private get _hasForecast(): boolean | undefined { + const stateObj = this._stateObj as WeatherEntity; + if (stateObj && stateObj.state !== UNAVAILABLE) { + return !!( + stateObj.attributes.forecast?.length || + stateObj.attributes.supported_features + ); + } + return undefined; + } + + private _forecastSupported(forecastType: ForecastType): boolean { + const stateObj = this._stateObj as WeatherEntity; + if (forecastType === "legacy") { + return !!stateObj.attributes.forecast?.length; + } + if (forecastType === "daily") { + return supportsFeature(stateObj, WeatherEntityFeature.FORECAST_DAILY); + } + if (forecastType === "hourly") { + return supportsFeature(stateObj, WeatherEntityFeature.FORECAST_HOURLY); + } + if (forecastType === "twice_daily") { + return supportsFeature( + stateObj, + WeatherEntityFeature.FORECAST_TWICE_DAILY + ); + } + return false; + } + private _schema = memoizeOne( - (localize: LocalizeFunc, hasForecast?: boolean) => + ( + localize: LocalizeFunc, + hasForecastLegacy?: boolean, + hasForecastDaily?: boolean, + hasForecastHourly?: boolean, + hasForecastTwiceDaily?: boolean + ) => [ { name: "entity", @@ -86,7 +141,54 @@ export class HuiWeatherForecastCardEditor { name: "theme", selector: { theme: {} } }, ], }, - ...(hasForecast + ...(!hasForecastLegacy && + (hasForecastDaily || hasForecastHourly || hasForecastTwiceDaily) + ? ([ + { + name: "forecast_type", + selector: { + select: { + options: [ + ...(hasForecastDaily + ? ([ + { + value: "daily", + label: localize( + "ui.panel.lovelace.editor.card.weather-forecast.daily" + ), + }, + ] as const) + : []), + ...(hasForecastHourly + ? ([ + { + value: "hourly", + label: localize( + "ui.panel.lovelace.editor.card.weather-forecast.hourly" + ), + }, + ] as const) + : []), + ...(hasForecastTwiceDaily + ? ([ + { + value: "twice_daily", + label: localize( + "ui.panel.lovelace.editor.card.weather-forecast.twice_daily" + ), + }, + ] as const) + : []), + ], + }, + }, + }, + ] as const) + : []), + ...(hasForecastDaily || + hasForecastHourly || + hasForecastTwiceDaily || + hasForecastLegacy ? ([ { name: "forecast", @@ -125,11 +227,17 @@ export class HuiWeatherForecastCardEditor return nothing; } - const schema = this._schema(this.hass.localize, this._has_forecast); + const schema = this._schema( + this.hass.localize, + this._forecastSupported("legacy"), + this._forecastSupported("daily"), + this._forecastSupported("hourly"), + this._forecastSupported("twice_daily") + ); const data: WeatherForecastCardConfig = { show_current: true, - show_forecast: this._has_forecast, + show_forecast: this._hasForecast, ...this._config, }; @@ -184,6 +292,10 @@ export class HuiWeatherForecastCardEditor )} (${this.hass!.localize( "ui.panel.lovelace.editor.card.config.optional" )})`; + case "forecast_type": + return this.hass!.localize( + "ui.panel.lovelace.editor.card.weather-forecast.forecast_type" + ); case "forecast": return this.hass!.localize( "ui.panel.lovelace.editor.card.weather-forecast.weather_to_show" 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 f8badf7b10..90351e76d6 100644 --- a/src/panels/lovelace/entity-rows/hui-weather-entity-row.ts +++ b/src/panels/lovelace/entity-rows/hui-weather-entity-row.ts @@ -16,9 +16,13 @@ import "../../../components/entity/state-badge"; import { isUnavailableState } from "../../../data/entity"; import { ActionHandlerEvent } from "../../../data/lovelace"; import { + getDefaultForecastType, + getForecast, getSecondaryWeatherAttribute, getWeatherStateIcon, getWeatherUnit, + subscribeForecast, + ForecastEvent, WeatherEntity, weatherSVGStyles, } from "../../../data/weather"; @@ -38,6 +42,48 @@ class HuiWeatherEntityRow extends LitElement implements LovelaceRow { @state() private _config?: EntitiesCardEntityConfig; + @state() private _forecastEvent?: ForecastEvent; + + @state() private _subscribed?: Promise<() => void>; + + private _unsubscribeForecastEvents() { + if (this._subscribed) { + this._subscribed.then((unsub) => unsub()); + this._subscribed = undefined; + } + } + + private async _subscribeForecastEvents() { + this._unsubscribeForecastEvents(); + if (!this.hass || !this._config || !this.isConnected) { + return; + } + const stateObj = this.hass!.states[this._config!.entity]; + const forecastType = getDefaultForecastType(stateObj); + if (forecastType) { + this._subscribed = subscribeForecast( + this.hass!, + stateObj.entity_id, + forecastType, + (event) => { + this._forecastEvent = event; + } + ); + } + } + + public connectedCallback() { + super.connectedCallback(); + if (this.hasUpdated) { + this._subscribeForecastEvents(); + } + } + + public disconnectedCallback(): void { + super.disconnectedCallback(); + this._unsubscribeForecastEvents(); + } + public setConfig(config: EntitiesCardEntityConfig): void { if (!config?.entity) { throw new Error("Entity must be specified"); @@ -50,6 +96,13 @@ class HuiWeatherEntityRow extends LitElement implements LovelaceRow { return hasConfigOrEntityChanged(this, changedProps); } + protected updated(changedProps: PropertyValues): void { + super.updated(changedProps); + if (changedProps.has("_config") || !this._subscribed) { + this._subscribeForecastEvents(); + } + } + protected render() { if (!this.hass || !this._config) { return nothing; @@ -72,6 +125,9 @@ class HuiWeatherEntityRow extends LitElement implements LovelaceRow { const hasSecondary = this._config.secondary_info; const weatherStateIcon = getWeatherStateIcon(stateObj.state, this); + const forecastData = getForecast(stateObj.attributes, this._forecastEvent); + const forecast = forecastData?.forecast; + return html`
- ${getSecondaryWeatherAttribute(this.hass!, stateObj)} + ${getSecondaryWeatherAttribute(this.hass!, stateObj, forecast!)}
`; diff --git a/src/translations/en.json b/src/translations/en.json index cad64bcb46..a77a941d7e 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -254,6 +254,9 @@ "day": "Day", "night": "Night", "forecast": "Forecast", + "forecast_daily": "Forecast daily", + "forecast_hourly": "Forecast hourly", + "forecast_twice_daily": "Forecast twice daily", "high": "High", "low": "Low" } @@ -4975,7 +4978,12 @@ "weather_to_show": "Weather to Show", "show_both": "Show current Weather and Forecast", "show_only_current": "Show only current Weather", - "show_only_forecast": "Show only Forecast" + "show_only_forecast": "Show only Forecast", + "forecast_type": "Select forecast type", + "no_type": "No type", + "daily": "Daily", + "hourly": "Hourly", + "twice_daily": "Twice daily" } }, "view": {