Get solar forecasts from energy platform (#9794)

* Get solar forecasts from energy platform

* Allow picking other forecasts in settings
This commit is contained in:
Paulus Schoutsen 2021-08-25 10:40:07 -07:00 committed by GitHub
parent b15684bcbd
commit 5bd92d04d9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 113 additions and 93 deletions

View File

@ -23,7 +23,6 @@ import { mockTranslations } from "./stubs/translations";
import { mockEnergy } from "./stubs/energy"; import { mockEnergy } from "./stubs/energy";
import { mockConfig } from "./stubs/config"; import { mockConfig } from "./stubs/config";
import { energyEntities } from "./stubs/entities"; import { energyEntities } from "./stubs/entities";
import { mockForecastSolar } from "./stubs/forecast_solar";
class HaDemo extends HomeAssistantAppEl { class HaDemo extends HomeAssistantAppEl {
protected async _initializeHass() { protected async _initializeHass() {
@ -52,7 +51,6 @@ class HaDemo extends HomeAssistantAppEl {
mockMediaPlayer(hass); mockMediaPlayer(hass);
mockFrontend(hass); mockFrontend(hass);
mockEnergy(hass); mockEnergy(hass);
mockForecastSolar(hass);
mockConfig(hass); mockConfig(hass);
mockPersistentNotification(hass); mockPersistentNotification(hass);

View File

@ -1,3 +1,5 @@
import { format, startOfToday, startOfTomorrow } from "date-fns";
import { EnergySolarForecasts } from "../../../src/data/energy";
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass"; import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
export const mockEnergy = (hass: MockHomeAssistant) => { export const mockEnergy = (hass: MockHomeAssistant) => {
@ -80,4 +82,53 @@ export const mockEnergy = (hass: MockHomeAssistant) => {
], ],
})); }));
hass.mockWS("energy/info", () => ({ cost_sensors: [] })); hass.mockWS("energy/info", () => ({ cost_sensors: [] }));
const todayString = format(startOfToday(), "yyyy-MM-dd");
const tomorrowString = format(startOfTomorrow(), "yyyy-MM-dd");
hass.mockWS(
"energy/solar_forecast",
(): EnergySolarForecasts => ({
solar_forecast: {
wh_hours: {
[`${todayString}T06:00:00`]: 0,
[`${todayString}T06:23:00`]: 6,
[`${todayString}T06:45:00`]: 39,
[`${todayString}T07:00:00`]: 28,
[`${todayString}T08:00:00`]: 208,
[`${todayString}T09:00:00`]: 352,
[`${todayString}T10:00:00`]: 544,
[`${todayString}T11:00:00`]: 748,
[`${todayString}T12:00:00`]: 1259,
[`${todayString}T13:00:00`]: 1361,
[`${todayString}T14:00:00`]: 1373,
[`${todayString}T15:00:00`]: 1370,
[`${todayString}T16:00:00`]: 1186,
[`${todayString}T17:00:00`]: 937,
[`${todayString}T18:00:00`]: 652,
[`${todayString}T19:00:00`]: 370,
[`${todayString}T20:00:00`]: 155,
[`${todayString}T21:48:00`]: 24,
[`${todayString}T22:36:00`]: 0,
[`${tomorrowString}T06:01:00`]: 0,
[`${tomorrowString}T06:23:00`]: 9,
[`${tomorrowString}T06:45:00`]: 47,
[`${tomorrowString}T07:00:00`]: 48,
[`${tomorrowString}T08:00:00`]: 473,
[`${tomorrowString}T09:00:00`]: 827,
[`${tomorrowString}T10:00:00`]: 1153,
[`${tomorrowString}T11:00:00`]: 1413,
[`${tomorrowString}T12:00:00`]: 1590,
[`${tomorrowString}T13:00:00`]: 1652,
[`${tomorrowString}T14:00:00`]: 1612,
[`${tomorrowString}T15:00:00`]: 1438,
[`${tomorrowString}T16:00:00`]: 1149,
[`${tomorrowString}T17:00:00`]: 830,
[`${tomorrowString}T18:00:00`]: 542,
[`${tomorrowString}T19:00:00`]: 311,
[`${tomorrowString}T20:00:00`]: 140,
[`${tomorrowString}T21:47:00`]: 22,
[`${tomorrowString}T22:34:00`]: 0,
},
},
})
);
}; };

View File

@ -1,55 +0,0 @@
import { format, startOfToday, startOfTomorrow } from "date-fns";
import { ForecastSolarForecast } from "../../../src/data/forecast_solar";
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
export const mockForecastSolar = (hass: MockHomeAssistant) => {
const todayString = format(startOfToday(), "yyyy-MM-dd");
const tomorrowString = format(startOfTomorrow(), "yyyy-MM-dd");
hass.mockWS(
"forecast_solar/forecasts",
(): Record<string, ForecastSolarForecast> => ({
solar_forecast: {
wh_hours: {
[`${todayString}T06:00:00`]: 0,
[`${todayString}T06:23:00`]: 6,
[`${todayString}T06:45:00`]: 39,
[`${todayString}T07:00:00`]: 28,
[`${todayString}T08:00:00`]: 208,
[`${todayString}T09:00:00`]: 352,
[`${todayString}T10:00:00`]: 544,
[`${todayString}T11:00:00`]: 748,
[`${todayString}T12:00:00`]: 1259,
[`${todayString}T13:00:00`]: 1361,
[`${todayString}T14:00:00`]: 1373,
[`${todayString}T15:00:00`]: 1370,
[`${todayString}T16:00:00`]: 1186,
[`${todayString}T17:00:00`]: 937,
[`${todayString}T18:00:00`]: 652,
[`${todayString}T19:00:00`]: 370,
[`${todayString}T20:00:00`]: 155,
[`${todayString}T21:48:00`]: 24,
[`${todayString}T22:36:00`]: 0,
[`${tomorrowString}T06:01:00`]: 0,
[`${tomorrowString}T06:23:00`]: 9,
[`${tomorrowString}T06:45:00`]: 47,
[`${tomorrowString}T07:00:00`]: 48,
[`${tomorrowString}T08:00:00`]: 473,
[`${tomorrowString}T09:00:00`]: 827,
[`${tomorrowString}T10:00:00`]: 1153,
[`${tomorrowString}T11:00:00`]: 1413,
[`${tomorrowString}T12:00:00`]: 1590,
[`${tomorrowString}T13:00:00`]: 1652,
[`${tomorrowString}T14:00:00`]: 1612,
[`${tomorrowString}T15:00:00`]: 1438,
[`${tomorrowString}T16:00:00`]: 1149,
[`${tomorrowString}T17:00:00`]: 830,
[`${tomorrowString}T18:00:00`]: 542,
[`${tomorrowString}T19:00:00`]: 311,
[`${tomorrowString}T20:00:00`]: 140,
[`${tomorrowString}T21:47:00`]: 22,
[`${tomorrowString}T22:34:00`]: 0,
},
},
})
);
};

View File

@ -63,6 +63,13 @@ export const emptyGasEnergyPreference = (): GasSourceTypeEnergyPreference => ({
number_energy_price: null, number_energy_price: null,
}); });
interface EnergySolarForecast {
wh_hours: Record<string, number>;
}
export type EnergySolarForecasts = {
[config_entry_id: string]: EnergySolarForecast;
};
export interface DeviceConsumptionEnergyPreference { export interface DeviceConsumptionEnergyPreference {
// This is an ever increasing value // This is an ever increasing value
stat_consumption: string; stat_consumption: string;
@ -143,6 +150,7 @@ export interface EnergyPreferences {
export interface EnergyInfo { export interface EnergyInfo {
cost_sensors: Record<string, string>; cost_sensors: Record<string, string>;
solar_forecast_domains: string[];
} }
export interface EnergyValidationIssue { export interface EnergyValidationIssue {
@ -440,3 +448,8 @@ export const getEnergyDataCollection = (
}; };
return collection; return collection;
}; };
export const getEnergySolarForecasts = (hass: HomeAssistant) =>
hass.callWS<EnergySolarForecasts>({
type: "energy/solar_forecast",
});

View File

@ -1,10 +0,0 @@
import { HomeAssistant } from "../types";
export interface ForecastSolarForecast {
wh_hours: Record<string, number>;
}
export const getForecastSolarForecasts = (hass: HomeAssistant) =>
hass.callWS<Record<string, ForecastSolarForecast>>({
type: "forecast_solar/forecasts",
});

View File

@ -6,6 +6,7 @@ import { fireEvent } from "../../../../common/dom/fire_event";
import { computeStateName } from "../../../../common/entity/compute_state_name"; import { computeStateName } from "../../../../common/entity/compute_state_name";
import "../../../../components/ha-card"; import "../../../../components/ha-card";
import { import {
EnergyInfo,
EnergyPreferences, EnergyPreferences,
EnergyPreferencesValidation, EnergyPreferencesValidation,
EnergyValidationIssue, EnergyValidationIssue,
@ -33,6 +34,9 @@ export class EnergySolarSettings extends LitElement {
@property({ attribute: false }) @property({ attribute: false })
public validationResult?: EnergyPreferencesValidation; public validationResult?: EnergyPreferencesValidation;
@property({ attribute: false })
public info?: EnergyInfo;
protected render(): TemplateResult { protected render(): TemplateResult {
const solarSources: SolarSourceTypeEnergyPreference[] = []; const solarSources: SolarSourceTypeEnergyPreference[] = [];
const solarValidation: EnergyValidationIssue[][] = []; const solarValidation: EnergyValidationIssue[][] = [];
@ -95,21 +99,29 @@ export class EnergySolarSettings extends LitElement {
? computeStateName(entityState) ? computeStateName(entityState)
: source.stat_energy_from}</span : source.stat_energy_from}</span
> >
${this.info
? html`
<mwc-icon-button @click=${this._editSource}> <mwc-icon-button @click=${this._editSource}>
<ha-svg-icon .path=${mdiPencil}></ha-svg-icon> <ha-svg-icon .path=${mdiPencil}></ha-svg-icon>
</mwc-icon-button> </mwc-icon-button>
`
: ""}
<mwc-icon-button @click=${this._deleteSource}> <mwc-icon-button @click=${this._deleteSource}>
<ha-svg-icon .path=${mdiDelete}></ha-svg-icon> <ha-svg-icon .path=${mdiDelete}></ha-svg-icon>
</mwc-icon-button> </mwc-icon-button>
</div> </div>
`; `;
})} })}
${this.info
? html`
<div class="row border-bottom"> <div class="row border-bottom">
<ha-svg-icon .path=${mdiSolarPower}></ha-svg-icon> <ha-svg-icon .path=${mdiSolarPower}></ha-svg-icon>
<mwc-button @click=${this._addSource} <mwc-button @click=${this._addSource}>
>Add solar production</mwc-button Add solar production
> </mwc-button>
</div> </div>
`
: ""}
</div> </div>
</ha-card> </ha-card>
`; `;
@ -117,6 +129,7 @@ export class EnergySolarSettings extends LitElement {
private _addSource() { private _addSource() {
showEnergySettingsSolarDialog(this, { showEnergySettingsSolarDialog(this, {
info: this.info!,
saveCallback: async (source) => { saveCallback: async (source) => {
await this._savePreferences({ await this._savePreferences({
...this.preferences, ...this.preferences,
@ -130,6 +143,7 @@ export class EnergySolarSettings extends LitElement {
const origSource: SolarSourceTypeEnergyPreference = const origSource: SolarSourceTypeEnergyPreference =
ev.currentTarget.closest(".row").source; ev.currentTarget.closest(".row").source;
showEnergySettingsSolarDialog(this, { showEnergySettingsSolarDialog(this, {
info: this.info!,
source: { ...origSource }, source: { ...origSource },
saveCallback: async (newSource) => { saveCallback: async (newSource) => {
await this._savePreferences({ await this._savePreferences({

View File

@ -44,8 +44,8 @@ export class DialogEnergySolarSettings
public async showDialog( public async showDialog(
params: EnergySettingsSolarDialogParams params: EnergySettingsSolarDialogParams
): Promise<void> { ): Promise<void> {
this._fetchForecastSolarConfigEntries();
this._params = params; this._params = params;
this._fetchSolarForecastConfigEntries();
this._source = params.source this._source = params.source
? { ...params.source } ? { ...params.source }
: (this._source = emptySolarEnergyPreference()); : (this._source = emptySolarEnergyPreference());
@ -118,7 +118,7 @@ export class DialogEnergySolarSettings
referrerpolicy="no-referrer" referrerpolicy="no-referrer"
style="height: 24px; margin-right: 16px;" style="height: 24px; margin-right: 16px;"
src=${brandsUrl({ src=${brandsUrl({
domain: "forecast_solar", domain: entry.domain,
type: "icon", type: "icon",
darkOptimized: this.hass.selectedTheme?.dark, darkOptimized: this.hass.selectedTheme?.dark,
})} })}
@ -155,9 +155,10 @@ export class DialogEnergySolarSettings
`; `;
} }
private async _fetchForecastSolarConfigEntries() { private async _fetchSolarForecastConfigEntries() {
this._configEntries = (await getConfigEntries(this.hass)).filter( const domains = this._params!.info.solar_forecast_domains;
(entry) => entry.domain === "forecast_solar" this._configEntries = (await getConfigEntries(this.hass)).filter((entry) =>
domains.includes(entry.domain)
); );
} }
@ -192,7 +193,7 @@ export class DialogEnergySolarSettings
this._source!.config_entry_solar_forecast = []; this._source!.config_entry_solar_forecast = [];
} }
this._source!.config_entry_solar_forecast.push(params.entryId); this._source!.config_entry_solar_forecast.push(params.entryId);
this._fetchForecastSolarConfigEntries(); this._fetchSolarForecastConfigEntries();
} }
}, },
}); });

View File

@ -2,6 +2,7 @@ import { fireEvent } from "../../../../common/dom/fire_event";
import { import {
BatterySourceTypeEnergyPreference, BatterySourceTypeEnergyPreference,
DeviceConsumptionEnergyPreference, DeviceConsumptionEnergyPreference,
EnergyInfo,
FlowFromGridSourceEnergyPreference, FlowFromGridSourceEnergyPreference,
FlowToGridSourceEnergyPreference, FlowToGridSourceEnergyPreference,
GasSourceTypeEnergyPreference, GasSourceTypeEnergyPreference,
@ -31,6 +32,7 @@ export interface EnergySettingsGridFlowToDialogParams {
} }
export interface EnergySettingsSolarDialogParams { export interface EnergySettingsSolarDialogParams {
info: EnergyInfo;
source?: SolarSourceTypeEnergyPreference; source?: SolarSourceTypeEnergyPreference;
saveCallback: (source: SolarSourceTypeEnergyPreference) => Promise<void>; saveCallback: (source: SolarSourceTypeEnergyPreference) => Promise<void>;
} }

View File

@ -2,10 +2,12 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import "../../../components/ha-svg-icon"; import "../../../components/ha-svg-icon";
import { import {
EnergyPreferences,
EnergyPreferencesValidation, EnergyPreferencesValidation,
getEnergyPreferences,
getEnergyPreferenceValidation, getEnergyPreferenceValidation,
EnergyInfo,
EnergyPreferences,
getEnergyInfo,
getEnergyPreferences,
} from "../../../data/energy"; } from "../../../data/energy";
import "../../../layouts/hass-loading-screen"; import "../../../layouts/hass-loading-screen";
import "../../../layouts/hass-tabs-subpage"; import "../../../layouts/hass-tabs-subpage";
@ -37,6 +39,8 @@ class HaConfigEnergy extends LitElement {
@state() private _searchParms = new URLSearchParams(window.location.search); @state() private _searchParms = new URLSearchParams(window.location.search);
@state() private _info?: EnergyInfo;
@state() private _preferences?: EnergyPreferences; @state() private _preferences?: EnergyPreferences;
@state() private _validationResult?: EnergyPreferencesValidation; @state() private _validationResult?: EnergyPreferencesValidation;
@ -90,6 +94,7 @@ class HaConfigEnergy extends LitElement {
.hass=${this.hass} .hass=${this.hass}
.preferences=${this._preferences!} .preferences=${this._preferences!}
.validationResult=${this._validationResult!} .validationResult=${this._validationResult!}
.info=${this._info}
@value-changed=${this._prefsChanged} @value-changed=${this._prefsChanged}
></ha-energy-solar-settings> ></ha-energy-solar-settings>
<ha-energy-battery-settings <ha-energy-battery-settings
@ -115,7 +120,10 @@ class HaConfigEnergy extends LitElement {
} }
private async _fetchConfig() { private async _fetchConfig() {
this._error = undefined;
const validationPromise = getEnergyPreferenceValidation(this.hass); const validationPromise = getEnergyPreferenceValidation(this.hass);
const energyInfoPromise = await getEnergyInfo(this.hass);
try { try {
this._preferences = await getEnergyPreferences(this.hass); this._preferences = await getEnergyPreferences(this.hass);
} catch (e) { } catch (e) {
@ -130,6 +138,7 @@ class HaConfigEnergy extends LitElement {
} catch (e) { } catch (e) {
this._error = e.message; this._error = e.message;
} }
this._info = await energyInfoPromise;
} }
private async _prefsChanged(ev: CustomEvent) { private async _prefsChanged(ev: CustomEvent) {
@ -140,6 +149,7 @@ class HaConfigEnergy extends LitElement {
} catch (e) { } catch (e) {
this._error = e.message; this._error = e.message;
} }
this._info = await getEnergyInfo(this.hass);
} }
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {

View File

@ -23,14 +23,11 @@ import {
import { labDarken } from "../../../../common/color/lab"; import { labDarken } from "../../../../common/color/lab";
import { import {
EnergyData, EnergyData,
EnergySolarForecasts,
getEnergyDataCollection, getEnergyDataCollection,
getEnergySolarForecasts,
SolarSourceTypeEnergyPreference, SolarSourceTypeEnergyPreference,
} from "../../../../data/energy"; } from "../../../../data/energy";
import { isComponentLoaded } from "../../../../common/config/is_component_loaded";
import {
ForecastSolarForecast,
getForecastSolarForecasts,
} from "../../../../data/forecast_solar";
import { computeStateName } from "../../../../common/entity/compute_state_name"; import { computeStateName } from "../../../../common/entity/compute_state_name";
import "../../../../components/chart/ha-chart-base"; import "../../../../components/chart/ha-chart-base";
import { import {
@ -218,13 +215,12 @@ export class HuiEnergySolarGraphCard
(source) => source.type === "solar" (source) => source.type === "solar"
) as SolarSourceTypeEnergyPreference[]; ) as SolarSourceTypeEnergyPreference[];
let forecasts: Record<string, ForecastSolarForecast>; let forecasts: EnergySolarForecasts | undefined;
if ( if (
isComponentLoaded(this.hass, "forecast_solar") && solarSources.some((source) => source.config_entry_solar_forecast?.length)
solarSources.some((source) => source.config_entry_solar_forecast)
) { ) {
try { try {
forecasts = await getForecastSolarForecasts(this.hass); forecasts = await getEnergySolarForecasts(this.hass);
} catch (_e) { } catch (_e) {
// ignore // ignore
} }