Implement forecast types for Weather (#15028)

* Implement forecast types

* editor

* Fix twice_daily

* All cards

* Review comments

* hasforecast

* card-editor

* forecast default

* Review comments

* fix entity row

* Remove legacy option

* Check if selected forecast is supported when picking entity

* Always show weather_to_show selector

* comments

* Update types.ts

* Hourly before twice-daily

* Expose forecast via WS instead of as state attributes

* Unsubscribe on disconnect

* lint

* prettier

* Fix _forecastSupported

* Improve conditions for subscribing to forecast updates

* Teach weather entity row and more info to subscribe

* Fix subscribing

* Deduplicate code in getForecast

* Simplify

* Tweak subscribe logic

* Address review comments

---------

Co-authored-by: Erik <erik@montnemery.com>
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
This commit is contained in:
G Johansson 2023-07-21 17:30:59 +02:00 committed by GitHub
parent 0eebc9095c
commit ec58862f3e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 460 additions and 56 deletions

View File

@ -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<ForecastEvent>(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;
};

View File

@ -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 {
</div>
`
: ""}
${this.stateObj.attributes.forecast
${forecast
? html`
<div class="section">
${this.hass.localize("ui.card.weather.forecast")}:
</div>
${this.stateObj.attributes.forecast.map((item) =>
${forecast.map((item) =>
this._showValue(item.templow) || this._showValue(item.temperature)
? html`<div class="flex">
${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")}
</div>
`}
<div class="templow">

View File

@ -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<LovelaceCardEditor> {
@ -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!
)}
</div>
</div>
</div>
@ -308,7 +357,7 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
{ weekday: "short" }
)}
<div class="daynight">
${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
)
)}
</div>

View File

@ -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;

View File

@ -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"

View File

@ -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`
<div
class="icon-image ${classMap({
@ -160,7 +216,7 @@ class HuiWeatherEntityRow extends LitElement implements LovelaceRow {
`}
</div>
<div class="secondary">
${getSecondaryWeatherAttribute(this.hass!, stateObj)}
${getSecondaryWeatherAttribute(this.hass!, stateObj, forecast!)}
</div>
</div>
`;

View File

@ -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": {