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 { mockConfig } from "./stubs/config";
import { energyEntities } from "./stubs/entities";
import { mockForecastSolar } from "./stubs/forecast_solar";
class HaDemo extends HomeAssistantAppEl {
protected async _initializeHass() {
@ -52,7 +51,6 @@ class HaDemo extends HomeAssistantAppEl {
mockMediaPlayer(hass);
mockFrontend(hass);
mockEnergy(hass);
mockForecastSolar(hass);
mockConfig(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";
export const mockEnergy = (hass: MockHomeAssistant) => {
@ -80,4 +82,53 @@ export const mockEnergy = (hass: MockHomeAssistant) => {
],
}));
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,
});
interface EnergySolarForecast {
wh_hours: Record<string, number>;
}
export type EnergySolarForecasts = {
[config_entry_id: string]: EnergySolarForecast;
};
export interface DeviceConsumptionEnergyPreference {
// This is an ever increasing value
stat_consumption: string;
@ -143,6 +150,7 @@ export interface EnergyPreferences {
export interface EnergyInfo {
cost_sensors: Record<string, string>;
solar_forecast_domains: string[];
}
export interface EnergyValidationIssue {
@ -440,3 +448,8 @@ export const getEnergyDataCollection = (
};
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 "../../../../components/ha-card";
import {
EnergyInfo,
EnergyPreferences,
EnergyPreferencesValidation,
EnergyValidationIssue,
@ -33,6 +34,9 @@ export class EnergySolarSettings extends LitElement {
@property({ attribute: false })
public validationResult?: EnergyPreferencesValidation;
@property({ attribute: false })
public info?: EnergyInfo;
protected render(): TemplateResult {
const solarSources: SolarSourceTypeEnergyPreference[] = [];
const solarValidation: EnergyValidationIssue[][] = [];
@ -95,21 +99,29 @@ export class EnergySolarSettings extends LitElement {
? computeStateName(entityState)
: source.stat_energy_from}</span
>
<mwc-icon-button @click=${this._editSource}>
<ha-svg-icon .path=${mdiPencil}></ha-svg-icon>
</mwc-icon-button>
${this.info
? html`
<mwc-icon-button @click=${this._editSource}>
<ha-svg-icon .path=${mdiPencil}></ha-svg-icon>
</mwc-icon-button>
`
: ""}
<mwc-icon-button @click=${this._deleteSource}>
<ha-svg-icon .path=${mdiDelete}></ha-svg-icon>
</mwc-icon-button>
</div>
`;
})}
<div class="row border-bottom">
<ha-svg-icon .path=${mdiSolarPower}></ha-svg-icon>
<mwc-button @click=${this._addSource}
>Add solar production</mwc-button
>
</div>
${this.info
? html`
<div class="row border-bottom">
<ha-svg-icon .path=${mdiSolarPower}></ha-svg-icon>
<mwc-button @click=${this._addSource}>
Add solar production
</mwc-button>
</div>
`
: ""}
</div>
</ha-card>
`;
@ -117,6 +129,7 @@ export class EnergySolarSettings extends LitElement {
private _addSource() {
showEnergySettingsSolarDialog(this, {
info: this.info!,
saveCallback: async (source) => {
await this._savePreferences({
...this.preferences,
@ -130,6 +143,7 @@ export class EnergySolarSettings extends LitElement {
const origSource: SolarSourceTypeEnergyPreference =
ev.currentTarget.closest(".row").source;
showEnergySettingsSolarDialog(this, {
info: this.info!,
source: { ...origSource },
saveCallback: async (newSource) => {
await this._savePreferences({

View File

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

View File

@ -2,6 +2,7 @@ import { fireEvent } from "../../../../common/dom/fire_event";
import {
BatterySourceTypeEnergyPreference,
DeviceConsumptionEnergyPreference,
EnergyInfo,
FlowFromGridSourceEnergyPreference,
FlowToGridSourceEnergyPreference,
GasSourceTypeEnergyPreference,
@ -31,6 +32,7 @@ export interface EnergySettingsGridFlowToDialogParams {
}
export interface EnergySettingsSolarDialogParams {
info: EnergyInfo;
source?: SolarSourceTypeEnergyPreference;
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 "../../../components/ha-svg-icon";
import {
EnergyPreferences,
EnergyPreferencesValidation,
getEnergyPreferences,
getEnergyPreferenceValidation,
EnergyInfo,
EnergyPreferences,
getEnergyInfo,
getEnergyPreferences,
} from "../../../data/energy";
import "../../../layouts/hass-loading-screen";
import "../../../layouts/hass-tabs-subpage";
@ -37,6 +39,8 @@ class HaConfigEnergy extends LitElement {
@state() private _searchParms = new URLSearchParams(window.location.search);
@state() private _info?: EnergyInfo;
@state() private _preferences?: EnergyPreferences;
@state() private _validationResult?: EnergyPreferencesValidation;
@ -90,6 +94,7 @@ class HaConfigEnergy extends LitElement {
.hass=${this.hass}
.preferences=${this._preferences!}
.validationResult=${this._validationResult!}
.info=${this._info}
@value-changed=${this._prefsChanged}
></ha-energy-solar-settings>
<ha-energy-battery-settings
@ -115,7 +120,10 @@ class HaConfigEnergy extends LitElement {
}
private async _fetchConfig() {
this._error = undefined;
const validationPromise = getEnergyPreferenceValidation(this.hass);
const energyInfoPromise = await getEnergyInfo(this.hass);
try {
this._preferences = await getEnergyPreferences(this.hass);
} catch (e) {
@ -130,6 +138,7 @@ class HaConfigEnergy extends LitElement {
} catch (e) {
this._error = e.message;
}
this._info = await energyInfoPromise;
}
private async _prefsChanged(ev: CustomEvent) {
@ -140,6 +149,7 @@ class HaConfigEnergy extends LitElement {
} catch (e) {
this._error = e.message;
}
this._info = await getEnergyInfo(this.hass);
}
static get styles(): CSSResultGroup {

View File

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