mirror of
https://github.com/home-assistant/frontend.git
synced 2025-04-26 06:17:20 +00:00
commit
68bec5e158
@ -1,5 +1,6 @@
|
|||||||
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
||||||
import { Lovelace } from "../../../src/panels/lovelace/types";
|
import { Lovelace } from "../../../src/panels/lovelace/types";
|
||||||
|
import { energyEntities } from "../stubs/entities";
|
||||||
import { DemoConfig } from "./types";
|
import { DemoConfig } from "./types";
|
||||||
|
|
||||||
export const demoConfigs: Array<() => Promise<DemoConfig>> = [
|
export const demoConfigs: Array<() => Promise<DemoConfig>> = [
|
||||||
@ -27,6 +28,7 @@ export const setDemoConfig = async (
|
|||||||
selectedDemoConfig = confProm;
|
selectedDemoConfig = confProm;
|
||||||
|
|
||||||
hass.addEntities(config.entities(hass.localize), true);
|
hass.addEntities(config.entities(hass.localize), true);
|
||||||
|
hass.addEntities(energyEntities());
|
||||||
lovelace.saveConfig(config.lovelace(hass.localize));
|
lovelace.saveConfig(config.lovelace(hass.localize));
|
||||||
hass.mockTheme(config.theme());
|
hass.mockTheme(config.theme());
|
||||||
};
|
};
|
||||||
|
@ -20,6 +20,10 @@ import { mockShoppingList } from "./stubs/shopping_list";
|
|||||||
import { mockSystemLog } from "./stubs/system_log";
|
import { mockSystemLog } from "./stubs/system_log";
|
||||||
import { mockTemplate } from "./stubs/template";
|
import { mockTemplate } from "./stubs/template";
|
||||||
import { mockTranslations } from "./stubs/translations";
|
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 {
|
class HaDemo extends HomeAssistantAppEl {
|
||||||
protected async _initializeHass() {
|
protected async _initializeHass() {
|
||||||
@ -47,8 +51,13 @@ class HaDemo extends HomeAssistantAppEl {
|
|||||||
mockEvents(hass);
|
mockEvents(hass);
|
||||||
mockMediaPlayer(hass);
|
mockMediaPlayer(hass);
|
||||||
mockFrontend(hass);
|
mockFrontend(hass);
|
||||||
|
mockEnergy(hass);
|
||||||
|
mockForecastSolar(hass);
|
||||||
|
mockConfig(hass);
|
||||||
mockPersistentNotification(hass);
|
mockPersistentNotification(hass);
|
||||||
|
|
||||||
|
hass.addEntities(energyEntities());
|
||||||
|
|
||||||
// Once config is loaded AND localize, set entities and apply theme.
|
// Once config is loaded AND localize, set entities and apply theme.
|
||||||
Promise.all([selectedDemoConfig, localizePromise]).then(
|
Promise.all([selectedDemoConfig, localizePromise]).then(
|
||||||
([conf, localize]) => {
|
([conf, localize]) => {
|
||||||
|
41
demo/src/stubs/config.ts
Normal file
41
demo/src/stubs/config.ts
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
||||||
|
|
||||||
|
export const mockConfig = (hass: MockHomeAssistant) => {
|
||||||
|
hass.mockAPI("config/config_entries/entry", () => [
|
||||||
|
{
|
||||||
|
entry_id: "co2signal",
|
||||||
|
domain: "co2signal",
|
||||||
|
title: "CO2 Signal",
|
||||||
|
source: "user",
|
||||||
|
state: "loaded",
|
||||||
|
supports_options: false,
|
||||||
|
supports_unload: true,
|
||||||
|
pref_disable_new_entities: false,
|
||||||
|
pref_disable_polling: false,
|
||||||
|
disabled_by: null,
|
||||||
|
reason: null,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
hass.mockWS("config/entity_registry/list", () => [
|
||||||
|
{
|
||||||
|
config_entry_id: "co2signal",
|
||||||
|
device_id: "co2signal",
|
||||||
|
area_id: null,
|
||||||
|
disabled_by: null,
|
||||||
|
entity_id: "sensor.co2_intensity",
|
||||||
|
name: null,
|
||||||
|
icon: null,
|
||||||
|
platform: "co2signal",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
config_entry_id: "co2signal",
|
||||||
|
device_id: "co2signal",
|
||||||
|
area_id: null,
|
||||||
|
disabled_by: null,
|
||||||
|
entity_id: "sensor.grid_fossil_fuel_percentage",
|
||||||
|
name: null,
|
||||||
|
icon: null,
|
||||||
|
platform: "co2signal",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
};
|
70
demo/src/stubs/energy.ts
Normal file
70
demo/src/stubs/energy.ts
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
||||||
|
|
||||||
|
export const mockEnergy = (hass: MockHomeAssistant) => {
|
||||||
|
hass.mockWS("energy/get_prefs", () => ({
|
||||||
|
energy_sources: [
|
||||||
|
{
|
||||||
|
type: "grid",
|
||||||
|
flow_from: [
|
||||||
|
{
|
||||||
|
stat_energy_from: "sensor.energy_consumption_tarif_1",
|
||||||
|
stat_cost: "sensor.energy_consumption_tarif_1_cost",
|
||||||
|
entity_energy_from: "sensor.energy_consumption_tarif_1",
|
||||||
|
entity_energy_price: null,
|
||||||
|
number_energy_price: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
stat_energy_from: "sensor.energy_consumption_tarif_2",
|
||||||
|
stat_cost: "sensor.energy_consumption_tarif_2_cost",
|
||||||
|
entity_energy_from: "sensor.energy_consumption_tarif_2",
|
||||||
|
entity_energy_price: null,
|
||||||
|
number_energy_price: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
flow_to: [
|
||||||
|
{
|
||||||
|
stat_energy_to: "sensor.energy_production_tarif_1",
|
||||||
|
stat_compensation: "sensor.energy_production_tarif_1_compensation",
|
||||||
|
entity_energy_to: "sensor.energy_production_tarif_1",
|
||||||
|
entity_energy_price: null,
|
||||||
|
number_energy_price: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
stat_energy_to: "sensor.energy_production_tarif_2",
|
||||||
|
stat_compensation: "sensor.energy_production_tarif_2_compensation",
|
||||||
|
entity_energy_to: "sensor.energy_production_tarif_2",
|
||||||
|
entity_energy_price: null,
|
||||||
|
number_energy_price: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
cost_adjustment_day: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "solar",
|
||||||
|
stat_energy_from: "sensor.solar_production",
|
||||||
|
config_entry_solar_forecast: ["solar_forecast"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
device_consumption: [
|
||||||
|
{
|
||||||
|
stat_consumption: "sensor.energy_car",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
stat_consumption: "sensor.energy_ac",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
stat_consumption: "sensor.energy_washing_machine",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
stat_consumption: "sensor.energy_dryer",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
stat_consumption: "sensor.energy_heat_pump",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
stat_consumption: "sensor.energy_boiler",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}));
|
||||||
|
hass.mockWS("energy/info", () => ({ cost_sensors: [] }));
|
||||||
|
};
|
143
demo/src/stubs/entities.ts
Normal file
143
demo/src/stubs/entities.ts
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
import { convertEntities } from "../../../src/fake_data/entity";
|
||||||
|
|
||||||
|
export const energyEntities = () =>
|
||||||
|
convertEntities({
|
||||||
|
"sensor.grid_fossil_fuel_percentage": {
|
||||||
|
entity_id: "sensor.grid_fossil_fuel_percentage",
|
||||||
|
state: "88.6",
|
||||||
|
attributes: {
|
||||||
|
unit_of_measurement: "%",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"sensor.solar_production": {
|
||||||
|
entity_id: "sensor.solar_production",
|
||||||
|
state: "88.6",
|
||||||
|
attributes: {
|
||||||
|
last_reset: "1970-01-01T00:00:00:00+00",
|
||||||
|
friendly_name: "Solar",
|
||||||
|
unit_of_measurement: "kWh",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"sensor.energy_consumption_tarif_1": {
|
||||||
|
entity_id: "sensor.energy_consumption_tarif_1 ",
|
||||||
|
state: "88.6",
|
||||||
|
attributes: {
|
||||||
|
last_reset: "1970-01-01T00:00:00:00+00",
|
||||||
|
friendly_name: "Grid consumption low tariff",
|
||||||
|
unit_of_measurement: "kWh",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"sensor.energy_consumption_tarif_2": {
|
||||||
|
entity_id: "sensor.energy_consumption_tarif_2",
|
||||||
|
state: "88.6",
|
||||||
|
attributes: {
|
||||||
|
last_reset: "1970-01-01T00:00:00:00+00",
|
||||||
|
friendly_name: "Grid consumption high tariff",
|
||||||
|
unit_of_measurement: "kWh",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"sensor.energy_production_tarif_1": {
|
||||||
|
entity_id: "sensor.energy_production_tarif_1",
|
||||||
|
state: "88.6",
|
||||||
|
attributes: {
|
||||||
|
last_reset: "1970-01-01T00:00:00:00+00",
|
||||||
|
friendly_name: "Returned to grid low tariff",
|
||||||
|
unit_of_measurement: "kWh",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"sensor.energy_production_tarif_2": {
|
||||||
|
entity_id: "sensor.energy_production_tarif_2",
|
||||||
|
state: "88.6",
|
||||||
|
attributes: {
|
||||||
|
last_reset: "1970-01-01T00:00:00:00+00",
|
||||||
|
friendly_name: "Returned to grid high tariff",
|
||||||
|
unit_of_measurement: "kWh",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"sensor.energy_consumption_tarif_1_cost": {
|
||||||
|
entity_id: "sensor.energy_consumption_tarif_1_cost",
|
||||||
|
state: "2",
|
||||||
|
attributes: {
|
||||||
|
last_reset: "1970-01-01T00:00:00:00+00",
|
||||||
|
unit_of_measurement: "EUR",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"sensor.energy_consumption_tarif_2_cost": {
|
||||||
|
entity_id: "sensor.energy_consumption_tarif_2_cost",
|
||||||
|
state: "2",
|
||||||
|
attributes: {
|
||||||
|
last_reset: "1970-01-01T00:00:00:00+00",
|
||||||
|
unit_of_measurement: "EUR",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"sensor.energy_production_tarif_1_compensation": {
|
||||||
|
entity_id: "sensor.energy_production_tarif_1_compensation",
|
||||||
|
state: "2",
|
||||||
|
attributes: {
|
||||||
|
last_reset: "1970-01-01T00:00:00:00+00",
|
||||||
|
unit_of_measurement: "EUR",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"sensor.energy_production_tarif_2_compensation": {
|
||||||
|
entity_id: "sensor.energy_production_tarif_2_compensation",
|
||||||
|
state: "2",
|
||||||
|
attributes: {
|
||||||
|
last_reset: "1970-01-01T00:00:00:00+00",
|
||||||
|
unit_of_measurement: "EUR",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"sensor.energy_car": {
|
||||||
|
entity_id: "sensor.energy_car",
|
||||||
|
state: "4",
|
||||||
|
attributes: {
|
||||||
|
last_reset: "1970-01-01T00:00:00:00+00",
|
||||||
|
friendly_name: "Electric car",
|
||||||
|
unit_of_measurement: "kWh",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"sensor.energy_ac": {
|
||||||
|
entity_id: "sensor.energy_ac",
|
||||||
|
state: "3",
|
||||||
|
attributes: {
|
||||||
|
last_reset: "1970-01-01T00:00:00:00+00",
|
||||||
|
friendly_name: "Air conditioning",
|
||||||
|
unit_of_measurement: "kWh",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"sensor.energy_washing_machine": {
|
||||||
|
entity_id: "sensor.energy_washing_machine",
|
||||||
|
state: "6",
|
||||||
|
attributes: {
|
||||||
|
last_reset: "1970-01-01T00:00:00:00+00",
|
||||||
|
friendly_name: "Washing machine",
|
||||||
|
unit_of_measurement: "kWh",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"sensor.energy_dryer": {
|
||||||
|
entity_id: "sensor.energy_dryer",
|
||||||
|
state: "5.5",
|
||||||
|
attributes: {
|
||||||
|
last_reset: "1970-01-01T00:00:00:00+00",
|
||||||
|
friendly_name: "Dryer",
|
||||||
|
unit_of_measurement: "kWh",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"sensor.energy_heat_pump": {
|
||||||
|
entity_id: "sensor.energy_heat_pump",
|
||||||
|
state: "6",
|
||||||
|
attributes: {
|
||||||
|
last_reset: "1970-01-01T00:00:00:00+00",
|
||||||
|
friendly_name: "Heat pump",
|
||||||
|
unit_of_measurement: "kWh",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"sensor.energy_boiler": {
|
||||||
|
entity_id: "sensor.energy_boiler",
|
||||||
|
state: "7",
|
||||||
|
attributes: {
|
||||||
|
last_reset: "1970-01-01T00:00:00:00+00",
|
||||||
|
friendly_name: "Boiler",
|
||||||
|
unit_of_measurement: "kWh",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
55
demo/src/stubs/forecast_solar.ts
Normal file
55
demo/src/stubs/forecast_solar.ts
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
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,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
@ -1,4 +1,6 @@
|
|||||||
|
import { addHours, differenceInHours } from "date-fns";
|
||||||
import { HassEntity } from "home-assistant-js-websocket";
|
import { HassEntity } from "home-assistant-js-websocket";
|
||||||
|
import { StatisticValue } from "../../../src/data/history";
|
||||||
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
||||||
|
|
||||||
interface HistoryQueryParams {
|
interface HistoryQueryParams {
|
||||||
@ -64,6 +66,211 @@ const generateHistory = (state, deltas) => {
|
|||||||
|
|
||||||
const incrementalUnits = ["clients", "queries", "ads"];
|
const incrementalUnits = ["clients", "queries", "ads"];
|
||||||
|
|
||||||
|
const generateMeanStatistics = (
|
||||||
|
id: string,
|
||||||
|
start: Date,
|
||||||
|
end: Date,
|
||||||
|
initValue: number,
|
||||||
|
maxDiff: number
|
||||||
|
) => {
|
||||||
|
const statistics: StatisticValue[] = [];
|
||||||
|
let currentDate = new Date(start);
|
||||||
|
currentDate.setMinutes(0, 0, 0);
|
||||||
|
let lastVal = initValue;
|
||||||
|
const now = new Date();
|
||||||
|
while (end > currentDate && currentDate < now) {
|
||||||
|
const delta = Math.random() * maxDiff;
|
||||||
|
const mean = lastVal + delta;
|
||||||
|
statistics.push({
|
||||||
|
statistic_id: id,
|
||||||
|
start: currentDate.toISOString(),
|
||||||
|
mean,
|
||||||
|
min: mean,
|
||||||
|
max: mean,
|
||||||
|
last_reset: "1970-01-01T00:00:00+00:00",
|
||||||
|
state: mean,
|
||||||
|
sum: null,
|
||||||
|
});
|
||||||
|
lastVal = mean;
|
||||||
|
currentDate = addHours(currentDate, 1);
|
||||||
|
}
|
||||||
|
return statistics;
|
||||||
|
};
|
||||||
|
|
||||||
|
const generateSumStatistics = (
|
||||||
|
id: string,
|
||||||
|
start: Date,
|
||||||
|
end: Date,
|
||||||
|
initValue: number,
|
||||||
|
maxDiff: number
|
||||||
|
) => {
|
||||||
|
const statistics: StatisticValue[] = [];
|
||||||
|
let currentDate = new Date(start);
|
||||||
|
currentDate.setMinutes(0, 0, 0);
|
||||||
|
let sum = initValue;
|
||||||
|
const now = new Date();
|
||||||
|
while (end > currentDate && currentDate < now) {
|
||||||
|
const add = Math.random() * maxDiff;
|
||||||
|
sum += add;
|
||||||
|
statistics.push({
|
||||||
|
statistic_id: id,
|
||||||
|
start: currentDate.toISOString(),
|
||||||
|
mean: null,
|
||||||
|
min: null,
|
||||||
|
max: null,
|
||||||
|
last_reset: "1970-01-01T00:00:00+00:00",
|
||||||
|
state: initValue + sum,
|
||||||
|
sum,
|
||||||
|
});
|
||||||
|
currentDate = addHours(currentDate, 1);
|
||||||
|
}
|
||||||
|
return statistics;
|
||||||
|
};
|
||||||
|
|
||||||
|
const generateCurvedStatistics = (
|
||||||
|
id: string,
|
||||||
|
start: Date,
|
||||||
|
end: Date,
|
||||||
|
initValue: number,
|
||||||
|
maxDiff: number,
|
||||||
|
metered: boolean
|
||||||
|
) => {
|
||||||
|
const statistics: StatisticValue[] = [];
|
||||||
|
let currentDate = new Date(start);
|
||||||
|
currentDate.setMinutes(0, 0, 0);
|
||||||
|
let sum = initValue;
|
||||||
|
const hours = differenceInHours(end, start) - 1;
|
||||||
|
let i = 0;
|
||||||
|
let half = false;
|
||||||
|
const now = new Date();
|
||||||
|
while (end > currentDate && currentDate < now) {
|
||||||
|
const add = Math.random() * maxDiff;
|
||||||
|
sum += i * add;
|
||||||
|
statistics.push({
|
||||||
|
statistic_id: id,
|
||||||
|
start: currentDate.toISOString(),
|
||||||
|
mean: null,
|
||||||
|
min: null,
|
||||||
|
max: null,
|
||||||
|
last_reset: "1970-01-01T00:00:00+00:00",
|
||||||
|
state: initValue + sum,
|
||||||
|
sum: metered ? sum : null,
|
||||||
|
});
|
||||||
|
currentDate = addHours(currentDate, 1);
|
||||||
|
if (!half && i > hours / 2) {
|
||||||
|
half = true;
|
||||||
|
}
|
||||||
|
i += half ? -1 : 1;
|
||||||
|
}
|
||||||
|
return statistics;
|
||||||
|
};
|
||||||
|
|
||||||
|
const statisticsFunctions: Record<
|
||||||
|
string,
|
||||||
|
(id: string, start: Date, end: Date) => StatisticValue[]
|
||||||
|
> = {
|
||||||
|
"sensor.energy_consumption_tarif_1": (id: string, start: Date, end: Date) => {
|
||||||
|
const morningEnd = new Date(start.getTime() + 10 * 60 * 60 * 1000);
|
||||||
|
const morningLow = generateSumStatistics(id, start, morningEnd, 0, 0.7);
|
||||||
|
const eveningStart = new Date(start.getTime() + 20 * 60 * 60 * 1000);
|
||||||
|
const morningFinalVal = morningLow.length
|
||||||
|
? morningLow[morningLow.length - 1].sum!
|
||||||
|
: 0;
|
||||||
|
const empty = generateSumStatistics(
|
||||||
|
id,
|
||||||
|
morningEnd,
|
||||||
|
eveningStart,
|
||||||
|
morningFinalVal,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
const eveningLow = generateSumStatistics(
|
||||||
|
id,
|
||||||
|
eveningStart,
|
||||||
|
end,
|
||||||
|
morningFinalVal,
|
||||||
|
0.7
|
||||||
|
);
|
||||||
|
return [...morningLow, ...empty, ...eveningLow];
|
||||||
|
},
|
||||||
|
"sensor.energy_consumption_tarif_2": (id: string, start: Date, end: Date) => {
|
||||||
|
const morningEnd = new Date(start.getTime() + 9 * 60 * 60 * 1000);
|
||||||
|
const eveningStart = new Date(start.getTime() + 20 * 60 * 60 * 1000);
|
||||||
|
const highTarif = generateSumStatistics(
|
||||||
|
id,
|
||||||
|
morningEnd,
|
||||||
|
eveningStart,
|
||||||
|
0,
|
||||||
|
0.3
|
||||||
|
);
|
||||||
|
const highTarifFinalVal = highTarif.length
|
||||||
|
? highTarif[highTarif.length - 1].sum!
|
||||||
|
: 0;
|
||||||
|
const morning = generateSumStatistics(id, start, morningEnd, 0, 0);
|
||||||
|
const evening = generateSumStatistics(
|
||||||
|
id,
|
||||||
|
eveningStart,
|
||||||
|
end,
|
||||||
|
highTarifFinalVal,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
return [...morning, ...highTarif, ...evening];
|
||||||
|
},
|
||||||
|
"sensor.energy_production_tarif_1": (id, start, end) =>
|
||||||
|
generateSumStatistics(id, start, end, 0, 0),
|
||||||
|
"sensor.energy_production_tarif_1_compensation": (id, start, end) =>
|
||||||
|
generateSumStatistics(id, start, end, 0, 0),
|
||||||
|
"sensor.energy_production_tarif_2": (id, start, end) => {
|
||||||
|
const productionStart = new Date(start.getTime() + 9 * 60 * 60 * 1000);
|
||||||
|
const productionEnd = new Date(start.getTime() + 21 * 60 * 60 * 1000);
|
||||||
|
const production = generateCurvedStatistics(
|
||||||
|
id,
|
||||||
|
productionStart,
|
||||||
|
productionEnd,
|
||||||
|
0,
|
||||||
|
0.15,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
const productionFinalVal = production.length
|
||||||
|
? production[production.length - 1].sum!
|
||||||
|
: 0;
|
||||||
|
const morning = generateSumStatistics(id, start, productionStart, 0, 0);
|
||||||
|
const evening = generateSumStatistics(
|
||||||
|
id,
|
||||||
|
productionEnd,
|
||||||
|
end,
|
||||||
|
productionFinalVal,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
return [...morning, ...production, ...evening];
|
||||||
|
},
|
||||||
|
"sensor.solar_production": (id, start, end) => {
|
||||||
|
const productionStart = new Date(start.getTime() + 7 * 60 * 60 * 1000);
|
||||||
|
const productionEnd = new Date(start.getTime() + 23 * 60 * 60 * 1000);
|
||||||
|
const production = generateCurvedStatistics(
|
||||||
|
id,
|
||||||
|
productionStart,
|
||||||
|
productionEnd,
|
||||||
|
0,
|
||||||
|
0.3,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
const productionFinalVal = production.length
|
||||||
|
? production[production.length - 1].sum!
|
||||||
|
: 0;
|
||||||
|
const morning = generateSumStatistics(id, start, productionStart, 0, 0);
|
||||||
|
const evening = generateSumStatistics(
|
||||||
|
id,
|
||||||
|
productionEnd,
|
||||||
|
end,
|
||||||
|
productionFinalVal,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
return [...morning, ...production, ...evening];
|
||||||
|
},
|
||||||
|
"sensor.grid_fossil_fuel_percentage": (id, start, end) =>
|
||||||
|
generateMeanStatistics(id, start, end, 35, 1.3),
|
||||||
|
};
|
||||||
|
|
||||||
export const mockHistory = (mockHass: MockHomeAssistant) => {
|
export const mockHistory = (mockHass: MockHomeAssistant) => {
|
||||||
mockHass.mockAPI(
|
mockHass.mockAPI(
|
||||||
new RegExp("history/period/.+"),
|
new RegExp("history/period/.+"),
|
||||||
@ -133,4 +340,39 @@ export const mockHistory = (mockHass: MockHomeAssistant) => {
|
|||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
mockHass.mockWS(
|
||||||
|
"history/statistics_during_period",
|
||||||
|
({ statistic_ids, start_time, end_time }, hass) => {
|
||||||
|
const start = new Date(start_time);
|
||||||
|
const end = new Date(end_time);
|
||||||
|
|
||||||
|
const statistics: Record<string, StatisticValue[]> = {};
|
||||||
|
|
||||||
|
statistic_ids.forEach((id: string) => {
|
||||||
|
if (id in statisticsFunctions) {
|
||||||
|
statistics[id] = statisticsFunctions[id](id, start, end);
|
||||||
|
} else {
|
||||||
|
const entityState = hass.states[id];
|
||||||
|
const state = entityState ? Number(entityState.state) : 1;
|
||||||
|
statistics[id] =
|
||||||
|
entityState && "last_reset" in entityState.attributes
|
||||||
|
? generateSumStatistics(
|
||||||
|
id,
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
state,
|
||||||
|
state * (state > 80 ? 0.01 : 0.05)
|
||||||
|
)
|
||||||
|
: generateMeanStatistics(
|
||||||
|
id,
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
state,
|
||||||
|
state * (state > 80 ? 0.05 : 0.1)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return statistics;
|
||||||
|
}
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
@ -6,7 +6,7 @@ export const mockTemplate = (hass: MockHomeAssistant) => {
|
|||||||
body: { message: "Template dev tool does not work in the demo." },
|
body: { message: "Template dev tool does not work in the demo." },
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
hass.mockWS("render_template", (msg, onChange) => {
|
hass.mockWS("render_template", (msg, _hass, onChange) => {
|
||||||
onChange!({
|
onChange!({
|
||||||
result: msg.template,
|
result: msg.template,
|
||||||
listeners: { all: false, domains: [], entities: [], time: false },
|
listeners: { all: false, domains: [], entities: [], time: false },
|
||||||
|
2
setup.py
2
setup.py
@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
|||||||
|
|
||||||
setup(
|
setup(
|
||||||
name="home-assistant-frontend",
|
name="home-assistant-frontend",
|
||||||
version="20210803.0",
|
version="20210803.1",
|
||||||
description="The Home Assistant frontend",
|
description="The Home Assistant frontend",
|
||||||
url="https://github.com/home-assistant/frontend",
|
url="https://github.com/home-assistant/frontend",
|
||||||
author="The Home Assistant Authors",
|
author="The Home Assistant Authors",
|
||||||
|
@ -228,7 +228,7 @@ const getEnergyData = async (
|
|||||||
|
|
||||||
const stats = await fetchStatistics(hass!, addHours(start, -1), end, statIDs); // Subtract 1 hour from start to get starting point data
|
const stats = await fetchStatistics(hass!, addHours(start, -1), end, statIDs); // Subtract 1 hour from start to get starting point data
|
||||||
|
|
||||||
return {
|
const data = {
|
||||||
start,
|
start,
|
||||||
end,
|
end,
|
||||||
info,
|
info,
|
||||||
@ -237,6 +237,8 @@ const getEnergyData = async (
|
|||||||
co2SignalConfigEntry,
|
co2SignalConfigEntry,
|
||||||
co2SignalEntity,
|
co2SignalEntity,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface EnergyCollection extends Collection<EnergyData> {
|
export interface EnergyCollection extends Collection<EnergyData> {
|
||||||
@ -300,10 +302,10 @@ export const getEnergyDataCollection = (
|
|||||||
// Schedule a refresh for 20 minutes past the hour
|
// Schedule a refresh for 20 minutes past the hour
|
||||||
// If the end is larger than the current time.
|
// If the end is larger than the current time.
|
||||||
const nextFetch = new Date();
|
const nextFetch = new Date();
|
||||||
if (nextFetch.getMinutes() > 20) {
|
if (nextFetch.getMinutes() >= 20) {
|
||||||
nextFetch.setHours(nextFetch.getHours() + 1);
|
nextFetch.setHours(nextFetch.getHours() + 1);
|
||||||
}
|
}
|
||||||
nextFetch.setMinutes(20);
|
nextFetch.setMinutes(20, 0, 0);
|
||||||
|
|
||||||
collection._refreshTimeout = window.setTimeout(
|
collection._refreshTimeout = window.setTimeout(
|
||||||
() => collection.refresh(),
|
() => collection.refresh(),
|
||||||
@ -360,15 +362,15 @@ export const getEnergyDataCollection = (
|
|||||||
collection.setPeriod = (newStart: Date, newEnd?: Date) => {
|
collection.setPeriod = (newStart: Date, newEnd?: Date) => {
|
||||||
collection.start = newStart;
|
collection.start = newStart;
|
||||||
collection.end = newEnd;
|
collection.end = newEnd;
|
||||||
if (collection._updatePeriodTimeout) {
|
|
||||||
clearTimeout(collection._updatePeriodTimeout);
|
|
||||||
collection._updatePeriodTimeout = undefined;
|
|
||||||
}
|
|
||||||
if (
|
if (
|
||||||
collection.start.getTime() === startOfToday().getTime() &&
|
collection.start.getTime() === startOfToday().getTime() &&
|
||||||
collection.end?.getTime() === endOfToday().getTime()
|
collection.end?.getTime() === endOfToday().getTime() &&
|
||||||
|
!collection._updatePeriodTimeout
|
||||||
) {
|
) {
|
||||||
scheduleUpdatePeriod();
|
scheduleUpdatePeriod();
|
||||||
|
} else if (collection._updatePeriodTimeout) {
|
||||||
|
clearTimeout(collection._updatePeriodTimeout);
|
||||||
|
collection._updatePeriodTimeout = undefined;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
return collection;
|
return collection;
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { Connection, createCollection } from "home-assistant-js-websocket";
|
import { Connection, createCollection } from "home-assistant-js-websocket";
|
||||||
|
import { Store } from "home-assistant-js-websocket/dist/store";
|
||||||
import { computeStateName } from "../common/entity/compute_state_name";
|
import { computeStateName } from "../common/entity/compute_state_name";
|
||||||
import { debounce } from "../common/util/debounce";
|
import { debounce } from "../common/util/debounce";
|
||||||
import { HomeAssistant } from "../types";
|
import { HomeAssistant } from "../types";
|
||||||
@ -96,12 +97,15 @@ export const removeEntityRegistryEntry = (
|
|||||||
entity_id: entityId,
|
entity_id: entityId,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const fetchEntityRegistry = (conn) =>
|
export const fetchEntityRegistry = (conn: Connection) =>
|
||||||
conn.sendMessagePromise({
|
conn.sendMessagePromise<EntityRegistryEntry[]>({
|
||||||
type: "config/entity_registry/list",
|
type: "config/entity_registry/list",
|
||||||
});
|
});
|
||||||
|
|
||||||
const subscribeEntityRegistryUpdates = (conn, store) =>
|
const subscribeEntityRegistryUpdates = (
|
||||||
|
conn: Connection,
|
||||||
|
store: Store<EntityRegistryEntry[]>
|
||||||
|
) =>
|
||||||
conn.subscribeEvents(
|
conn.subscribeEvents(
|
||||||
debounce(
|
debounce(
|
||||||
() =>
|
() =>
|
||||||
|
@ -346,64 +346,32 @@ export const statisticsHaveType = (
|
|||||||
type: StatisticType
|
type: StatisticType
|
||||||
) => stats.some((stat) => stat[type] !== null);
|
) => stats.some((stat) => stat[type] !== null);
|
||||||
|
|
||||||
/**
|
// Merge the growth of multiple sum statistics into one
|
||||||
* Get the earliest start from a list of statistics.
|
const mergeSumGrowthStatistics = (stats: StatisticValue[][]) => {
|
||||||
*/
|
const result = {};
|
||||||
const getMinStatisticStart = (stats: StatisticValue[][]): string | null => {
|
|
||||||
let earliestString: string | null = null;
|
|
||||||
let earliestTime: Date | null = null;
|
|
||||||
|
|
||||||
for (const stat of stats) {
|
stats.forEach((stat) => {
|
||||||
if (stat.length === 0) {
|
if (stat.length === 0) {
|
||||||
continue;
|
return;
|
||||||
}
|
}
|
||||||
const curTime = new Date(stat[0].start);
|
let prevSum: number | null = null;
|
||||||
|
stat.forEach((statVal) => {
|
||||||
if (earliestString === null) {
|
if (statVal.sum === null) {
|
||||||
earliestString = stat[0].start;
|
return;
|
||||||
earliestTime = curTime;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (curTime < earliestTime!) {
|
|
||||||
earliestString = stat[0].start;
|
|
||||||
earliestTime = curTime;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return earliestString;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Merge multiple sum statistics into one
|
|
||||||
const mergeSumStatistics = (stats: StatisticValue[][]) => {
|
|
||||||
const result: { start: string; sum: number }[] = [];
|
|
||||||
|
|
||||||
const statsCopy: StatisticValue[][] = stats.map((stat) => [...stat]);
|
|
||||||
|
|
||||||
while (statsCopy.some((stat) => stat.length > 0)) {
|
|
||||||
const earliestStart = getMinStatisticStart(statsCopy)!;
|
|
||||||
|
|
||||||
let sum = 0;
|
|
||||||
|
|
||||||
for (const stat of statsCopy) {
|
|
||||||
if (stat.length === 0) {
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
if (stat[0].start !== earliestStart) {
|
if (prevSum === null) {
|
||||||
continue;
|
prevSum = statVal.sum;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
const statVal = stat.shift()!;
|
const growth = statVal.sum - prevSum;
|
||||||
if (!statVal.sum) {
|
if (statVal.start in result) {
|
||||||
continue;
|
result[statVal.start] += growth;
|
||||||
|
} else {
|
||||||
|
result[statVal.start] = growth;
|
||||||
}
|
}
|
||||||
sum += statVal.sum;
|
prevSum = statVal.sum;
|
||||||
}
|
|
||||||
|
|
||||||
result.push({
|
|
||||||
start: earliestStart,
|
|
||||||
sum,
|
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
@ -418,55 +386,23 @@ export const calculateStatisticsSumGrowthWithPercentage = (
|
|||||||
): number | null => {
|
): number | null => {
|
||||||
let sum: number | null = null;
|
let sum: number | null = null;
|
||||||
|
|
||||||
if (sumStats.length === 0) {
|
if (sumStats.length === 0 || percentageStat.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const sumStatsToProcess = mergeSumStatistics(sumStats);
|
const sumGrowthToProcess = mergeSumGrowthStatistics(sumStats);
|
||||||
const percentageStatToProcess = [...percentageStat];
|
|
||||||
|
|
||||||
let lastSum: number | null = null;
|
percentageStat.forEach((percentageStatValue) => {
|
||||||
|
const sumGrowth = sumGrowthToProcess[percentageStatValue.start];
|
||||||
// pre-populate lastSum with last sum statistic _before_ the first percentage statistic
|
if (sumGrowth === undefined) {
|
||||||
for (const stat of sumStatsToProcess) {
|
return;
|
||||||
if (new Date(stat.start) >= new Date(percentageStat[0].start)) {
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
lastSum = stat.sum;
|
if (sum === null) {
|
||||||
}
|
sum = sumGrowth * (percentageStatValue.mean! / 100);
|
||||||
|
} else {
|
||||||
while (percentageStatToProcess.length > 0) {
|
sum += sumGrowth * (percentageStatValue.mean! / 100);
|
||||||
if (!sumStatsToProcess.length) {
|
|
||||||
return sum;
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
// If they are not equal, pop the value that is earlier in time
|
|
||||||
if (sumStatsToProcess[0].start !== percentageStatToProcess[0].start) {
|
|
||||||
if (
|
|
||||||
new Date(sumStatsToProcess[0].start) <
|
|
||||||
new Date(percentageStatToProcess[0].start)
|
|
||||||
) {
|
|
||||||
sumStatsToProcess.shift();
|
|
||||||
} else {
|
|
||||||
percentageStatToProcess.shift();
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const sumStatValue = sumStatsToProcess.shift()!;
|
|
||||||
const percentageStatValue = percentageStatToProcess.shift()!;
|
|
||||||
|
|
||||||
if (lastSum !== null) {
|
|
||||||
const sumGrowth = sumStatValue.sum! - lastSum;
|
|
||||||
if (sum === null) {
|
|
||||||
sum = sumGrowth * (percentageStatValue.mean! / 100);
|
|
||||||
} else {
|
|
||||||
sum += sumGrowth * (percentageStatValue.mean! / 100);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lastSum = sumStatValue.sum;
|
|
||||||
}
|
|
||||||
|
|
||||||
return sum;
|
return sum;
|
||||||
};
|
};
|
||||||
|
@ -11,7 +11,13 @@ export const demoConfig: HassConfig = {
|
|||||||
temperature: "°C",
|
temperature: "°C",
|
||||||
volume: "L",
|
volume: "L",
|
||||||
},
|
},
|
||||||
components: ["notify.html5", "history", "shopping_list"],
|
components: [
|
||||||
|
"notify.html5",
|
||||||
|
"history",
|
||||||
|
"shopping_list",
|
||||||
|
"forecast_solar",
|
||||||
|
"energy",
|
||||||
|
],
|
||||||
time_zone: "America/Los_Angeles",
|
time_zone: "America/Los_Angeles",
|
||||||
config_dir: "/config",
|
config_dir: "/config",
|
||||||
version: "DEMO",
|
version: "DEMO",
|
||||||
|
@ -72,6 +72,13 @@ export const demoPanels: Panels = {
|
|||||||
config: null,
|
config: null,
|
||||||
url_path: "map",
|
url_path: "map",
|
||||||
},
|
},
|
||||||
|
energy: {
|
||||||
|
component_name: "energy",
|
||||||
|
icon: "hass:lightning-bolt",
|
||||||
|
title: "energy",
|
||||||
|
config: null,
|
||||||
|
url_path: "energy",
|
||||||
|
},
|
||||||
// config: {
|
// config: {
|
||||||
// component_name: "config",
|
// component_name: "config",
|
||||||
// icon: "hass:cog",
|
// icon: "hass:cog",
|
||||||
|
@ -33,7 +33,11 @@ export interface MockHomeAssistant extends HomeAssistant {
|
|||||||
addTranslations(translations: Record<string, string>, language?: string);
|
addTranslations(translations: Record<string, string>, language?: string);
|
||||||
mockWS(
|
mockWS(
|
||||||
type: string,
|
type: string,
|
||||||
callback: (msg: any, onChange?: (response: any) => void) => any
|
callback: (
|
||||||
|
msg: any,
|
||||||
|
hass: MockHomeAssistant,
|
||||||
|
onChange?: (response: any) => void
|
||||||
|
) => any
|
||||||
);
|
);
|
||||||
mockAPI(path: string | RegExp, callback: MockRestCallback);
|
mockAPI(path: string | RegExp, callback: MockRestCallback);
|
||||||
mockEvent(event);
|
mockEvent(event);
|
||||||
@ -144,7 +148,7 @@ export const provideHass = (
|
|||||||
const callback = wsCommands[msg.type];
|
const callback = wsCommands[msg.type];
|
||||||
|
|
||||||
if (callback) {
|
if (callback) {
|
||||||
callback(msg);
|
callback(msg, hass());
|
||||||
} else {
|
} else {
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
console.error(`Unknown WS command: ${msg.type}`);
|
console.error(`Unknown WS command: ${msg.type}`);
|
||||||
@ -153,7 +157,7 @@ export const provideHass = (
|
|||||||
sendMessagePromise: async (msg) => {
|
sendMessagePromise: async (msg) => {
|
||||||
const callback = wsCommands[msg.type];
|
const callback = wsCommands[msg.type];
|
||||||
return callback
|
return callback
|
||||||
? callback(msg)
|
? callback(msg, hass())
|
||||||
: Promise.reject({
|
: Promise.reject({
|
||||||
code: "command_not_mocked",
|
code: "command_not_mocked",
|
||||||
message: `WS Command ${msg.type} is not implemented in provide_hass.`,
|
message: `WS Command ${msg.type} is not implemented in provide_hass.`,
|
||||||
@ -162,7 +166,7 @@ export const provideHass = (
|
|||||||
subscribeMessage: async (onChange, msg) => {
|
subscribeMessage: async (onChange, msg) => {
|
||||||
const callback = wsCommands[msg.type];
|
const callback = wsCommands[msg.type];
|
||||||
return callback
|
return callback
|
||||||
? callback(msg, onChange)
|
? callback(msg, hass(), onChange)
|
||||||
: Promise.reject({
|
: Promise.reject({
|
||||||
code: "command_not_mocked",
|
code: "command_not_mocked",
|
||||||
message: `WS Command ${msg.type} is not implemented in provide_hass.`,
|
message: `WS Command ${msg.type} is not implemented in provide_hass.`,
|
||||||
@ -266,6 +270,7 @@ export const provideHass = (
|
|||||||
updateStates,
|
updateStates,
|
||||||
updateTranslations,
|
updateTranslations,
|
||||||
addTranslations,
|
addTranslations,
|
||||||
|
loadFragmentTranslation: async (_fragment: string) => hass().localize,
|
||||||
addEntities,
|
addEntities,
|
||||||
mockWS(type, callback) {
|
mockWS(type, callback) {
|
||||||
wsCommands[type] = callback;
|
wsCommands[type] = callback;
|
||||||
|
@ -64,6 +64,12 @@ class HaConfigEnergy extends LitElement {
|
|||||||
.route=${this.route}
|
.route=${this.route}
|
||||||
.tabs=${configSections.experiences}
|
.tabs=${configSections.experiences}
|
||||||
>
|
>
|
||||||
|
<ha-card>
|
||||||
|
<div class="card-content">
|
||||||
|
After setting up a new device, it can take up to 2 hours for new
|
||||||
|
data to arrive in your energy dashboard.
|
||||||
|
</div>
|
||||||
|
</ha-card>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<ha-energy-grid-settings
|
<ha-energy-grid-settings
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
@ -112,7 +118,7 @@ class HaConfigEnergy extends LitElement {
|
|||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
|
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
|
||||||
grid-gap: 8px 8px;
|
grid-gap: 8px 8px;
|
||||||
padding: 8px;
|
margin: 8px;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
|
@ -54,6 +54,15 @@ class PanelEnergy extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected updated(changedProps: PropertyValues) {
|
||||||
|
if (
|
||||||
|
changedProps.has("narrow") &&
|
||||||
|
changedProps.get("narrow") !== undefined
|
||||||
|
) {
|
||||||
|
this._reloadView();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
<ha-app-layout>
|
<ha-app-layout>
|
||||||
@ -123,6 +132,7 @@ class PanelEnergy extends LitElement {
|
|||||||
hui-energy-period-selector {
|
hui-energy-period-selector {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding-left: 16px;
|
padding-left: 16px;
|
||||||
|
--disabled-text-color: rgba(var(--rgb-text-primary-color), 0.5);
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
|
@ -4,6 +4,7 @@ import {
|
|||||||
ChartOptions,
|
ChartOptions,
|
||||||
ParsedDataType,
|
ParsedDataType,
|
||||||
} from "chart.js";
|
} from "chart.js";
|
||||||
|
import { getRelativePosition } from "chart.js/helpers";
|
||||||
import { addHours } from "date-fns";
|
import { addHours } from "date-fns";
|
||||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
@ -11,6 +12,7 @@ import { customElement, property, state } from "lit/decorators";
|
|||||||
import { classMap } from "lit/directives/class-map";
|
import { classMap } from "lit/directives/class-map";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { getColorByIndex } from "../../../../common/color/colors";
|
import { getColorByIndex } from "../../../../common/color/colors";
|
||||||
|
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||||
import { computeStateName } from "../../../../common/entity/compute_state_name";
|
import { computeStateName } from "../../../../common/entity/compute_state_name";
|
||||||
import {
|
import {
|
||||||
formatNumber,
|
formatNumber,
|
||||||
@ -120,6 +122,18 @@ export class HuiEnergyDevicesGraphCard
|
|||||||
},
|
},
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
locale: numberFormatToLocale(this.hass.locale),
|
locale: numberFormatToLocale(this.hass.locale),
|
||||||
|
onClick: (e: any) => {
|
||||||
|
const chart = e.chart;
|
||||||
|
const canvasPosition = getRelativePosition(e, chart);
|
||||||
|
|
||||||
|
const index = Math.abs(
|
||||||
|
chart.scales.y.getValueForPixel(canvasPosition.y)
|
||||||
|
);
|
||||||
|
fireEvent(this, "hass-more-info", {
|
||||||
|
// @ts-ignore
|
||||||
|
entityId: this._chartData?.datasets[0]?.data[index]?.entity_id,
|
||||||
|
});
|
||||||
|
},
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -139,7 +153,7 @@ export class HuiEnergyDevicesGraphCard
|
|||||||
endTime = new Date(
|
endTime = new Date(
|
||||||
Math.max(
|
Math.max(
|
||||||
...statisticsData.map((stats) =>
|
...statisticsData.map((stats) =>
|
||||||
new Date(stats[stats.length - 1].start).getTime()
|
stats.length ? new Date(stats[stats.length - 1].start).getTime() : 0
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@ -162,19 +176,13 @@ export class HuiEnergyDevicesGraphCard
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
for (let idx = 0; idx < energyData.prefs.device_consumption.length; idx++) {
|
energyData.prefs.device_consumption.forEach((device, idx) => {
|
||||||
const device = energyData.prefs.device_consumption[idx];
|
|
||||||
const entity = this.hass.states[device.stat_consumption];
|
const entity = this.hass.states[device.stat_consumption];
|
||||||
const label = entity ? computeStateName(entity) : device.stat_consumption;
|
const label = entity ? computeStateName(entity) : device.stat_consumption;
|
||||||
|
|
||||||
const color = getColorByIndex(idx);
|
|
||||||
|
|
||||||
borderColor.push(color);
|
|
||||||
backgroundColor.push(color + "7F");
|
|
||||||
|
|
||||||
const value =
|
const value =
|
||||||
device.stat_consumption in this._data
|
device.stat_consumption in this._data!
|
||||||
? calculateStatisticSumGrowth(this._data[device.stat_consumption]) ||
|
? calculateStatisticSumGrowth(this._data![device.stat_consumption]) ||
|
||||||
0
|
0
|
||||||
: 0;
|
: 0;
|
||||||
|
|
||||||
@ -182,11 +190,20 @@ export class HuiEnergyDevicesGraphCard
|
|||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
y: label,
|
y: label,
|
||||||
x: value,
|
x: value,
|
||||||
|
entity_id: device.stat_consumption,
|
||||||
|
idx,
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
|
|
||||||
data.sort((a, b) => b.x - a.x);
|
data.sort((a, b) => b.x - a.x);
|
||||||
|
|
||||||
|
data.forEach((d: any) => {
|
||||||
|
const color = getColorByIndex(d.idx);
|
||||||
|
|
||||||
|
borderColor.push(color);
|
||||||
|
backgroundColor.push(color + "7F");
|
||||||
|
});
|
||||||
|
|
||||||
this._chartData = {
|
this._chartData = {
|
||||||
datasets,
|
datasets,
|
||||||
};
|
};
|
||||||
|
@ -10,7 +10,6 @@ import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
|||||||
import { css, html, LitElement, svg } from "lit";
|
import { css, html, LitElement, svg } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { classMap } from "lit/directives/class-map";
|
import { classMap } from "lit/directives/class-map";
|
||||||
import { ifDefined } from "lit/directives/if-defined";
|
|
||||||
import "@material/mwc-button";
|
import "@material/mwc-button";
|
||||||
import { formatNumber } from "../../../../common/string/format_number";
|
import { formatNumber } from "../../../../common/string/format_number";
|
||||||
import "../../../../components/ha-card";
|
import "../../../../components/ha-card";
|
||||||
@ -122,7 +121,8 @@ class HuiEnergyDistrubutionCard
|
|||||||
let homeLowCarbonCircumference: number | undefined;
|
let homeLowCarbonCircumference: number | undefined;
|
||||||
let homeHighCarbonCircumference: number | undefined;
|
let homeHighCarbonCircumference: number | undefined;
|
||||||
|
|
||||||
let electricityMapUrl: string | undefined;
|
// This fallback is used in the demo
|
||||||
|
let electricityMapUrl = "https://www.electricitymap.org";
|
||||||
|
|
||||||
if (
|
if (
|
||||||
this._data.co2SignalEntity &&
|
this._data.co2SignalEntity &&
|
||||||
@ -140,8 +140,8 @@ class HuiEnergyDistrubutionCard
|
|||||||
|
|
||||||
const co2State = this.hass.states[this._data.co2SignalEntity];
|
const co2State = this.hass.states[this._data.co2SignalEntity];
|
||||||
|
|
||||||
if (co2State) {
|
if (co2State?.attributes.country_code) {
|
||||||
electricityMapUrl = `https://www.electricitymap.org/zone/${co2State.attributes.country_code}`;
|
electricityMapUrl += `/zone/${co2State.attributes.country_code}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (highCarbonConsumption !== null) {
|
if (highCarbonConsumption !== null) {
|
||||||
@ -168,7 +168,7 @@ class HuiEnergyDistrubutionCard
|
|||||||
<span class="label">Non-fossil</span>
|
<span class="label">Non-fossil</span>
|
||||||
<a
|
<a
|
||||||
class="circle"
|
class="circle"
|
||||||
href=${ifDefined(electricityMapUrl)}
|
href=${electricityMapUrl}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener no referrer"
|
rel="noopener no referrer"
|
||||||
>
|
>
|
||||||
|
@ -98,10 +98,11 @@ class HuiEnergyGridGaugeCard
|
|||||||
<ha-card>
|
<ha-card>
|
||||||
<ha-svg-icon id="info" .path=${mdiInformation}></ha-svg-icon>
|
<ha-svg-icon id="info" .path=${mdiInformation}></ha-svg-icon>
|
||||||
<paper-tooltip animation-delay="0" for="info" position="left">
|
<paper-tooltip animation-delay="0" for="info" position="left">
|
||||||
This card represents your energy dependency. If it's green, it means
|
This card represents your energy dependency.
|
||||||
you produced more energy than that you consumed from the grid. If it's
|
<br /><br />
|
||||||
in the red, it means that you relied on the grid for part of your
|
If it's green, it means you produced more energy than that you
|
||||||
home's energy consumption.
|
consumed from the grid. If it's in the red, it means that you relied
|
||||||
|
on the grid for part of your home's energy consumption.
|
||||||
</paper-tooltip>
|
</paper-tooltip>
|
||||||
${value !== undefined
|
${value !== undefined
|
||||||
? html`<ha-gauge
|
? html`<ha-gauge
|
||||||
|
@ -88,10 +88,11 @@ class HuiEnergySolarGaugeCard
|
|||||||
<ha-card>
|
<ha-card>
|
||||||
<ha-svg-icon id="info" .path=${mdiInformation}></ha-svg-icon>
|
<ha-svg-icon id="info" .path=${mdiInformation}></ha-svg-icon>
|
||||||
<paper-tooltip animation-delay="0" for="info" position="left">
|
<paper-tooltip animation-delay="0" for="info" position="left">
|
||||||
This card represents how much of the solar energy was not used by your
|
This card represents how much of the solar energy was used by your
|
||||||
home and was returned to the grid. If you frequently return a lot, try
|
home and was not returned to the grid.
|
||||||
to conserve this energy by installing a battery or buying an electric
|
<br /><br />
|
||||||
car to charge.
|
If you frequently produce more than you consume, try to conserve this
|
||||||
|
energy by installing a battery or buying an electric car to charge.
|
||||||
</paper-tooltip>
|
</paper-tooltip>
|
||||||
${value !== undefined
|
${value !== undefined
|
||||||
? html`<ha-gauge
|
? html`<ha-gauge
|
||||||
|
@ -4,8 +4,13 @@ import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
|||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { classMap } from "lit/directives/class-map";
|
import { classMap } from "lit/directives/class-map";
|
||||||
import "../../../../components/ha-card";
|
import "../../../../components/ha-card";
|
||||||
import { ChartData, ChartDataset, ChartOptions } from "chart.js";
|
import {
|
||||||
import { endOfToday, startOfToday } from "date-fns";
|
ChartData,
|
||||||
|
ChartDataset,
|
||||||
|
ChartOptions,
|
||||||
|
ScatterDataPoint,
|
||||||
|
} from "chart.js";
|
||||||
|
import { endOfToday, isToday, startOfToday } from "date-fns";
|
||||||
import { HomeAssistant } from "../../../../types";
|
import { HomeAssistant } from "../../../../types";
|
||||||
import { LovelaceCard } from "../../types";
|
import { LovelaceCard } from "../../types";
|
||||||
import { EnergySolarGraphCardConfig } from "../types";
|
import { EnergySolarGraphCardConfig } from "../types";
|
||||||
@ -28,8 +33,6 @@ import {
|
|||||||
} from "../../../../data/forecast_solar";
|
} 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 "../../../../components/ha-switch";
|
|
||||||
import "../../../../components/ha-formfield";
|
|
||||||
import {
|
import {
|
||||||
formatNumber,
|
formatNumber,
|
||||||
numberFormatToLocale,
|
numberFormatToLocale,
|
||||||
@ -50,8 +53,6 @@ export class HuiEnergySolarGraphCard
|
|||||||
datasets: [],
|
datasets: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
@state() private _forecasts?: Record<string, ForecastSolarForecast>;
|
|
||||||
|
|
||||||
@state() private _start = startOfToday();
|
@state() private _start = startOfToday();
|
||||||
|
|
||||||
@state() private _end = endOfToday();
|
@state() private _end = endOfToday();
|
||||||
@ -96,6 +97,13 @@ export class HuiEnergySolarGraphCard
|
|||||||
)}
|
)}
|
||||||
chart-type="bar"
|
chart-type="bar"
|
||||||
></ha-chart-base>
|
></ha-chart-base>
|
||||||
|
${!this._chartData.datasets.length
|
||||||
|
? html`<div class="no-data">
|
||||||
|
${isToday(this._start)
|
||||||
|
? "There is no data to show. It can take up to 2 hours for new data to arrive after you configure your energy dashboard."
|
||||||
|
: "There is not data for this period."}
|
||||||
|
</div>`
|
||||||
|
: ""}
|
||||||
</div>
|
</div>
|
||||||
</ha-card>
|
</ha-card>
|
||||||
`;
|
`;
|
||||||
@ -188,11 +196,12 @@ export class HuiEnergySolarGraphCard
|
|||||||
(source) => source.type === "solar"
|
(source) => source.type === "solar"
|
||||||
) as SolarSourceTypeEnergyPreference[];
|
) as SolarSourceTypeEnergyPreference[];
|
||||||
|
|
||||||
|
let forecasts: Record<string, ForecastSolarForecast>;
|
||||||
if (
|
if (
|
||||||
isComponentLoaded(this.hass, "forecast_solar") &&
|
isComponentLoaded(this.hass, "forecast_solar") &&
|
||||||
solarSources.some((source) => source.config_entry_solar_forecast)
|
solarSources.some((source) => source.config_entry_solar_forecast)
|
||||||
) {
|
) {
|
||||||
this._forecasts = await getForecastSolarForecasts(this.hass);
|
forecasts = await getForecastSolarForecasts(this.hass);
|
||||||
}
|
}
|
||||||
|
|
||||||
const statisticsData = Object.values(energyData.stats);
|
const statisticsData = Object.values(energyData.stats);
|
||||||
@ -202,7 +211,7 @@ export class HuiEnergySolarGraphCard
|
|||||||
endTime = new Date(
|
endTime = new Date(
|
||||||
Math.max(
|
Math.max(
|
||||||
...statisticsData.map((stats) =>
|
...statisticsData.map((stats) =>
|
||||||
new Date(stats[stats.length - 1].start).getTime()
|
stats.length ? new Date(stats[stats.length - 1].start).getTime() : 0
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@ -225,18 +234,11 @@ export class HuiEnergySolarGraphCard
|
|||||||
? rgb2hex(lab2rgb(labDarken(rgb2lab(hex2rgb(solarColor)), idx)))
|
? rgb2hex(lab2rgb(labDarken(rgb2lab(hex2rgb(solarColor)), idx)))
|
||||||
: solarColor;
|
: solarColor;
|
||||||
|
|
||||||
data.push({
|
|
||||||
label: `Production ${
|
|
||||||
entity ? computeStateName(entity) : source.stat_energy_from
|
|
||||||
}`,
|
|
||||||
borderColor,
|
|
||||||
backgroundColor: borderColor + "7F",
|
|
||||||
data: [],
|
|
||||||
});
|
|
||||||
|
|
||||||
let prevValue: number | null = null;
|
let prevValue: number | null = null;
|
||||||
let prevStart: string | null = null;
|
let prevStart: string | null = null;
|
||||||
|
|
||||||
|
const solarProductionData: ScatterDataPoint[] = [];
|
||||||
|
|
||||||
// Process solar production data.
|
// Process solar production data.
|
||||||
if (energyData.stats[source.stat_energy_from]) {
|
if (energyData.stats[source.stat_energy_from]) {
|
||||||
for (const point of energyData.stats[source.stat_energy_from]) {
|
for (const point of energyData.stats[source.stat_energy_from]) {
|
||||||
@ -252,7 +254,7 @@ export class HuiEnergySolarGraphCard
|
|||||||
}
|
}
|
||||||
const value = point.sum - prevValue;
|
const value = point.sum - prevValue;
|
||||||
const date = new Date(point.start);
|
const date = new Date(point.start);
|
||||||
data[0].data.push({
|
solarProductionData.push({
|
||||||
x: date.getTime(),
|
x: date.getTime(),
|
||||||
y: value,
|
y: value,
|
||||||
});
|
});
|
||||||
@ -261,7 +263,16 @@ export class HuiEnergySolarGraphCard
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const forecasts = this._forecasts;
|
if (solarProductionData.length) {
|
||||||
|
data.push({
|
||||||
|
label: `Production ${
|
||||||
|
entity ? computeStateName(entity) : source.stat_energy_from
|
||||||
|
}`,
|
||||||
|
borderColor,
|
||||||
|
backgroundColor: borderColor + "7F",
|
||||||
|
data: solarProductionData,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Process solar forecast data.
|
// Process solar forecast data.
|
||||||
if (forecasts && source.config_entry_solar_forecast) {
|
if (forecasts && source.config_entry_solar_forecast) {
|
||||||
@ -271,6 +282,9 @@ export class HuiEnergySolarGraphCard
|
|||||||
forecastsData = forecasts![configEntryId]?.wh_hours;
|
forecastsData = forecasts![configEntryId]?.wh_hours;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!forecasts![configEntryId]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
Object.entries(forecasts![configEntryId].wh_hours).forEach(
|
Object.entries(forecasts![configEntryId].wh_hours).forEach(
|
||||||
([date, value]) => {
|
([date, value]) => {
|
||||||
if (date in forecastsData!) {
|
if (date in forecastsData!) {
|
||||||
@ -283,22 +297,7 @@ export class HuiEnergySolarGraphCard
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (forecastsData) {
|
if (forecastsData) {
|
||||||
const forecast: ChartDataset<"line"> = {
|
const solarForecastData: ScatterDataPoint[] = [];
|
||||||
type: "line",
|
|
||||||
label: `Forecast ${
|
|
||||||
entity ? computeStateName(entity) : source.stat_energy_from
|
|
||||||
}`,
|
|
||||||
fill: false,
|
|
||||||
stepped: false,
|
|
||||||
borderColor: computedStyles.getPropertyValue(
|
|
||||||
"--primary-text-color"
|
|
||||||
),
|
|
||||||
borderDash: [7, 5],
|
|
||||||
pointRadius: 0,
|
|
||||||
data: [],
|
|
||||||
};
|
|
||||||
data.push(forecast);
|
|
||||||
|
|
||||||
for (const [date, value] of Object.entries(forecastsData)) {
|
for (const [date, value] of Object.entries(forecastsData)) {
|
||||||
const dateObj = new Date(date);
|
const dateObj = new Date(date);
|
||||||
if (
|
if (
|
||||||
@ -307,11 +306,28 @@ export class HuiEnergySolarGraphCard
|
|||||||
) {
|
) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
forecast.data.push({
|
solarForecastData.push({
|
||||||
x: dateObj.getTime(),
|
x: dateObj.getTime(),
|
||||||
y: value / 1000,
|
y: value / 1000,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (solarForecastData.length) {
|
||||||
|
data.push({
|
||||||
|
type: "line",
|
||||||
|
label: `Forecast ${
|
||||||
|
entity ? computeStateName(entity) : source.stat_energy_from
|
||||||
|
}`,
|
||||||
|
fill: false,
|
||||||
|
stepped: false,
|
||||||
|
borderColor: computedStyles.getPropertyValue(
|
||||||
|
"--primary-text-color"
|
||||||
|
),
|
||||||
|
borderDash: [7, 5],
|
||||||
|
pointRadius: 0,
|
||||||
|
data: solarForecastData,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -341,8 +357,18 @@ export class HuiEnergySolarGraphCard
|
|||||||
.has-header {
|
.has-header {
|
||||||
padding-top: 0;
|
padding-top: 0;
|
||||||
}
|
}
|
||||||
ha-formfield {
|
.no-data {
|
||||||
margin-bottom: 16px;
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
padding: 20%;
|
||||||
|
margin-left: 32px;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { ChartData, ChartDataset, ChartOptions } from "chart.js";
|
import { ChartData, ChartDataset, ChartOptions } from "chart.js";
|
||||||
import { startOfToday, endOfToday } from "date-fns";
|
import { startOfToday, endOfToday, isToday } from "date-fns";
|
||||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
@ -84,6 +84,13 @@ export class HuiEnergyUsageGraphCard
|
|||||||
)}
|
)}
|
||||||
chart-type="bar"
|
chart-type="bar"
|
||||||
></ha-chart-base>
|
></ha-chart-base>
|
||||||
|
${!this._chartData.datasets.length
|
||||||
|
? html`<div class="no-data">
|
||||||
|
${isToday(this._start)
|
||||||
|
? "There is no data to show. It can take up to 2 hours for new data to arrive after you configure your energy dashboard."
|
||||||
|
: "There is not data for this period."}
|
||||||
|
</div>`
|
||||||
|
: ""}
|
||||||
</div>
|
</div>
|
||||||
</ha-card>
|
</ha-card>
|
||||||
`;
|
`;
|
||||||
@ -243,7 +250,7 @@ export class HuiEnergyUsageGraphCard
|
|||||||
endTime = new Date(
|
endTime = new Date(
|
||||||
Math.max(
|
Math.max(
|
||||||
...statisticsData.map((stats) =>
|
...statisticsData.map((stats) =>
|
||||||
new Date(stats[stats.length - 1].start).getTime()
|
stats.length ? new Date(stats[stats.length - 1].start).getTime() : 0
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@ -352,6 +359,7 @@ export class HuiEnergyUsageGraphCard
|
|||||||
: entity
|
: entity
|
||||||
? computeStateName(entity)
|
? computeStateName(entity)
|
||||||
: statId,
|
: statId,
|
||||||
|
order: type === "used_solar" ? 0 : idx + 1,
|
||||||
borderColor,
|
borderColor,
|
||||||
backgroundColor: hexBlend(borderColor, backgroundColor, 50),
|
backgroundColor: hexBlend(borderColor, backgroundColor, 50),
|
||||||
stack: "stack",
|
stack: "stack",
|
||||||
@ -393,6 +401,19 @@ export class HuiEnergyUsageGraphCard
|
|||||||
.has-header {
|
.has-header {
|
||||||
padding-top: 0;
|
padding-top: 0;
|
||||||
}
|
}
|
||||||
|
.no-data {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
padding: 20%;
|
||||||
|
margin-left: 32px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -104,14 +104,8 @@ export class HuiEnergyPeriodSelector extends SubscribeMixin(LitElement) {
|
|||||||
--mdc-theme-primary: currentColor;
|
--mdc-theme-primary: currentColor;
|
||||||
--mdc-button-outline-color: currentColor;
|
--mdc-button-outline-color: currentColor;
|
||||||
|
|
||||||
--mdc-button-disabled-outline-color: rgba(
|
--mdc-button-disabled-outline-color: var(--disabled-text-color);
|
||||||
var(--rgb-text-primary-color),
|
--mdc-button-disabled-ink-color: var(--disabled-text-color);
|
||||||
0.5
|
|
||||||
);
|
|
||||||
--mdc-button-disabled-ink-color: rgba(
|
|
||||||
var(--rgb-text-primary-color),
|
|
||||||
0.5
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@ -1003,7 +1003,6 @@
|
|||||||
"energy": {
|
"energy": {
|
||||||
"caption": "Energy",
|
"caption": "Energy",
|
||||||
"description": "Monitor your energy production and consumption",
|
"description": "Monitor your energy production and consumption",
|
||||||
"currency": "",
|
|
||||||
"grid": {
|
"grid": {
|
||||||
"title": "Electricity grid",
|
"title": "Electricity grid",
|
||||||
"sub": "Configure the amount of energy that you consume from the grid and, if you produce energy, give back to the grid. This allows Home Assistant to track your whole home energy usage.",
|
"sub": "Configure the amount of energy that you consume from the grid and, if you produce energy, give back to the grid. This allows Home Assistant to track your whole home energy usage.",
|
||||||
|
@ -10,7 +10,81 @@ describe("calculateStatisticsSumGrowthWithPercentage", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Returns null if not enough values", async () => {
|
it("Returns null if not enough sum stat values", async () => {
|
||||||
|
assert.strictEqual(
|
||||||
|
calculateStatisticsSumGrowthWithPercentage(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.carbon_intensity",
|
||||||
|
start: "2021-07-28T05:00:00Z",
|
||||||
|
last_reset: null,
|
||||||
|
max: 75,
|
||||||
|
mean: 50,
|
||||||
|
min: 25,
|
||||||
|
sum: null,
|
||||||
|
state: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.carbon_intensity",
|
||||||
|
start: "2021-07-28T07:00:00Z",
|
||||||
|
last_reset: null,
|
||||||
|
max: 100,
|
||||||
|
mean: 75,
|
||||||
|
min: 50,
|
||||||
|
sum: null,
|
||||||
|
state: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[]
|
||||||
|
),
|
||||||
|
null
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Returns null if not enough percentage stat values", async () => {
|
||||||
|
assert.strictEqual(
|
||||||
|
calculateStatisticsSumGrowthWithPercentage(
|
||||||
|
[],
|
||||||
|
[
|
||||||
|
[
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.peak_consumption",
|
||||||
|
start: "2021-07-28T04:00:00Z",
|
||||||
|
last_reset: null,
|
||||||
|
max: null,
|
||||||
|
mean: null,
|
||||||
|
min: null,
|
||||||
|
sum: 50,
|
||||||
|
state: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.peak_consumption",
|
||||||
|
start: "2021-07-28T05:00:00Z",
|
||||||
|
last_reset: null,
|
||||||
|
max: null,
|
||||||
|
mean: null,
|
||||||
|
min: null,
|
||||||
|
sum: 100,
|
||||||
|
state: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.peak_consumption",
|
||||||
|
start: "2021-07-28T07:00:00Z",
|
||||||
|
last_reset: null,
|
||||||
|
max: null,
|
||||||
|
mean: null,
|
||||||
|
min: null,
|
||||||
|
sum: 200,
|
||||||
|
state: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
]
|
||||||
|
),
|
||||||
|
null
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Returns a percentage of the growth", async () => {
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
calculateStatisticsSumGrowthWithPercentage(
|
calculateStatisticsSumGrowthWithPercentage(
|
||||||
[
|
[
|
||||||
@ -68,10 +142,435 @@ describe("calculateStatisticsSumGrowthWithPercentage", () => {
|
|||||||
state: null,
|
state: null,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[],
|
[
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.off_peak_consumption",
|
||||||
|
start: "2021-07-28T04:00:00Z",
|
||||||
|
last_reset: null,
|
||||||
|
max: null,
|
||||||
|
mean: null,
|
||||||
|
min: null,
|
||||||
|
sum: 50,
|
||||||
|
state: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.off_peak_consumption",
|
||||||
|
start: "2021-07-28T05:00:00Z",
|
||||||
|
last_reset: null,
|
||||||
|
max: null,
|
||||||
|
mean: null,
|
||||||
|
min: null,
|
||||||
|
sum: 100,
|
||||||
|
state: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.off_peak_consumption",
|
||||||
|
start: "2021-07-28T07:00:00Z",
|
||||||
|
last_reset: null,
|
||||||
|
max: null,
|
||||||
|
mean: null,
|
||||||
|
min: null,
|
||||||
|
sum: 200,
|
||||||
|
state: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
]
|
||||||
|
),
|
||||||
|
200
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("It ignores sum data that doesnt match start", async () => {
|
||||||
|
assert.strictEqual(
|
||||||
|
calculateStatisticsSumGrowthWithPercentage(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.carbon_intensity",
|
||||||
|
start: "2021-07-28T05:00:00Z",
|
||||||
|
last_reset: null,
|
||||||
|
max: 75,
|
||||||
|
mean: 50,
|
||||||
|
min: 25,
|
||||||
|
sum: null,
|
||||||
|
state: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.carbon_intensity",
|
||||||
|
start: "2021-07-28T07:00:00Z",
|
||||||
|
last_reset: null,
|
||||||
|
max: 100,
|
||||||
|
mean: 75,
|
||||||
|
min: 50,
|
||||||
|
sum: null,
|
||||||
|
state: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
[
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.peak_consumption",
|
||||||
|
start: "2021-07-28T04:00:00Z",
|
||||||
|
last_reset: null,
|
||||||
|
max: null,
|
||||||
|
mean: null,
|
||||||
|
min: null,
|
||||||
|
sum: 50,
|
||||||
|
state: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.peak_consumption",
|
||||||
|
start: "2021-07-28T04:00:00Z",
|
||||||
|
last_reset: null,
|
||||||
|
max: null,
|
||||||
|
mean: null,
|
||||||
|
min: null,
|
||||||
|
sum: 50,
|
||||||
|
state: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.peak_consumption",
|
||||||
|
start: "2021-07-28T05:00:00Z",
|
||||||
|
last_reset: null,
|
||||||
|
max: null,
|
||||||
|
mean: null,
|
||||||
|
min: null,
|
||||||
|
sum: 100,
|
||||||
|
state: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.peak_consumption",
|
||||||
|
start: "2021-07-28T07:00:00Z",
|
||||||
|
last_reset: null,
|
||||||
|
max: null,
|
||||||
|
mean: null,
|
||||||
|
min: null,
|
||||||
|
sum: 200,
|
||||||
|
state: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
100
|
100
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("It ignores percentage data that doesnt match start", async () => {
|
||||||
|
assert.strictEqual(
|
||||||
|
calculateStatisticsSumGrowthWithPercentage(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.carbon_intensity",
|
||||||
|
start: "2021-07-28T04:00:00Z",
|
||||||
|
last_reset: null,
|
||||||
|
max: 25,
|
||||||
|
mean: 25,
|
||||||
|
min: 25,
|
||||||
|
sum: null,
|
||||||
|
state: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.carbon_intensity",
|
||||||
|
start: "2021-07-28T05:00:00Z",
|
||||||
|
last_reset: null,
|
||||||
|
max: 75,
|
||||||
|
mean: 50,
|
||||||
|
min: 25,
|
||||||
|
sum: null,
|
||||||
|
state: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.carbon_intensity",
|
||||||
|
start: "2021-07-28T07:00:00Z",
|
||||||
|
last_reset: null,
|
||||||
|
max: 100,
|
||||||
|
mean: 75,
|
||||||
|
min: 50,
|
||||||
|
sum: null,
|
||||||
|
state: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
[
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.peak_consumption",
|
||||||
|
start: "2021-07-28T04:00:00Z",
|
||||||
|
last_reset: null,
|
||||||
|
max: null,
|
||||||
|
mean: null,
|
||||||
|
min: null,
|
||||||
|
sum: 50,
|
||||||
|
state: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.peak_consumption",
|
||||||
|
start: "2021-07-28T05:00:00Z",
|
||||||
|
last_reset: null,
|
||||||
|
max: null,
|
||||||
|
mean: null,
|
||||||
|
min: null,
|
||||||
|
sum: 100,
|
||||||
|
state: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.peak_consumption",
|
||||||
|
start: "2021-07-28T07:00:00Z",
|
||||||
|
last_reset: null,
|
||||||
|
max: null,
|
||||||
|
mean: null,
|
||||||
|
min: null,
|
||||||
|
sum: 200,
|
||||||
|
state: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
]
|
||||||
|
),
|
||||||
|
100
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Returns a percentage of the growth", async () => {
|
||||||
|
assert.strictEqual(
|
||||||
|
calculateStatisticsSumGrowthWithPercentage(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.grid_fossil_fuel_percentage",
|
||||||
|
start: "2021-08-03T06:00:00.000Z",
|
||||||
|
mean: 10,
|
||||||
|
min: 10,
|
||||||
|
max: 10,
|
||||||
|
last_reset: "1970-01-01T00:00:00+00:00",
|
||||||
|
state: 10,
|
||||||
|
sum: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.grid_fossil_fuel_percentage",
|
||||||
|
start: "2021-08-03T07:00:00.000Z",
|
||||||
|
mean: 20,
|
||||||
|
min: 20,
|
||||||
|
max: 20,
|
||||||
|
last_reset: "1970-01-01T00:00:00+00:00",
|
||||||
|
state: 20,
|
||||||
|
sum: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.grid_fossil_fuel_percentage",
|
||||||
|
start: "2021-08-03T08:00:00.000Z",
|
||||||
|
mean: 30,
|
||||||
|
min: 30,
|
||||||
|
max: 30,
|
||||||
|
last_reset: "1970-01-01T00:00:00+00:00",
|
||||||
|
state: 30,
|
||||||
|
sum: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.grid_fossil_fuel_percentage",
|
||||||
|
start: "2021-08-03T09:00:00.000Z",
|
||||||
|
mean: 40,
|
||||||
|
min: 40,
|
||||||
|
max: 40,
|
||||||
|
last_reset: "1970-01-01T00:00:00+00:00",
|
||||||
|
state: 40,
|
||||||
|
sum: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.grid_fossil_fuel_percentage",
|
||||||
|
start: "2021-08-03T10:00:00.000Z",
|
||||||
|
mean: 50,
|
||||||
|
min: 50,
|
||||||
|
max: 50,
|
||||||
|
last_reset: "1970-01-01T00:00:00+00:00",
|
||||||
|
state: 50,
|
||||||
|
sum: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.grid_fossil_fuel_percentage",
|
||||||
|
start: "2021-08-03T11:00:00.000Z",
|
||||||
|
mean: 60,
|
||||||
|
min: 60,
|
||||||
|
max: 60,
|
||||||
|
last_reset: "1970-01-01T00:00:00+00:00",
|
||||||
|
state: 60,
|
||||||
|
sum: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.grid_fossil_fuel_percentage",
|
||||||
|
start: "2021-08-03T12:00:00.000Z",
|
||||||
|
mean: 70,
|
||||||
|
min: 70,
|
||||||
|
max: 70,
|
||||||
|
last_reset: "1970-01-01T00:00:00+00:00",
|
||||||
|
state: 70,
|
||||||
|
sum: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.grid_fossil_fuel_percentage",
|
||||||
|
start: "2021-08-03T13:00:00.000Z",
|
||||||
|
mean: 80,
|
||||||
|
min: 80,
|
||||||
|
max: 80,
|
||||||
|
last_reset: "1970-01-01T00:00:00+00:00",
|
||||||
|
state: 80,
|
||||||
|
sum: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.grid_fossil_fuel_percentage",
|
||||||
|
start: "2021-08-03T14:00:00.000Z",
|
||||||
|
mean: 90,
|
||||||
|
min: 90,
|
||||||
|
max: 90,
|
||||||
|
last_reset: "1970-01-01T00:00:00+00:00",
|
||||||
|
state: 90,
|
||||||
|
sum: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.grid_fossil_fuel_percentage",
|
||||||
|
start: "2021-08-03T15:00:00.000Z",
|
||||||
|
mean: 100,
|
||||||
|
min: 100,
|
||||||
|
max: 100,
|
||||||
|
last_reset: "1970-01-01T00:00:00+00:00",
|
||||||
|
state: 100,
|
||||||
|
sum: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.grid_fossil_fuel_percentage",
|
||||||
|
start: "2021-08-03T16:00:00.000Z",
|
||||||
|
mean: 110,
|
||||||
|
min: 110,
|
||||||
|
max: 110,
|
||||||
|
last_reset: "1970-01-01T00:00:00+00:00",
|
||||||
|
state: 120,
|
||||||
|
sum: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
[
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.energy_consumption_tarif_1",
|
||||||
|
start: "2021-08-03T06:00:00.000Z",
|
||||||
|
mean: null,
|
||||||
|
min: null,
|
||||||
|
max: null,
|
||||||
|
last_reset: "1970-01-01T00:00:00+00:00",
|
||||||
|
state: 10,
|
||||||
|
sum: 10,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.energy_consumption_tarif_1",
|
||||||
|
start: "2021-08-03T07:00:00.000Z",
|
||||||
|
mean: null,
|
||||||
|
min: null,
|
||||||
|
max: null,
|
||||||
|
last_reset: "1970-01-01T00:00:00+00:00",
|
||||||
|
state: 20,
|
||||||
|
sum: 20,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.energy_consumption_tarif_1",
|
||||||
|
start: "2021-08-03T08:00:00.000Z",
|
||||||
|
mean: null,
|
||||||
|
min: null,
|
||||||
|
max: null,
|
||||||
|
last_reset: "1970-01-01T00:00:00+00:00",
|
||||||
|
state: 30,
|
||||||
|
sum: 30,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.energy_consumption_tarif_1",
|
||||||
|
start: "2021-08-03T09:00:00.000Z",
|
||||||
|
mean: null,
|
||||||
|
min: null,
|
||||||
|
max: null,
|
||||||
|
last_reset: "1970-01-01T00:00:00+00:00",
|
||||||
|
state: 40,
|
||||||
|
sum: 40,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.energy_consumption_tarif_1",
|
||||||
|
start: "2021-08-03T10:00:00.000Z",
|
||||||
|
mean: null,
|
||||||
|
min: null,
|
||||||
|
max: null,
|
||||||
|
last_reset: "1970-01-01T00:00:00+00:00",
|
||||||
|
state: 50,
|
||||||
|
sum: 50,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.energy_consumption_tarif_1",
|
||||||
|
start: "2021-08-03T11:00:00.000Z",
|
||||||
|
mean: null,
|
||||||
|
min: null,
|
||||||
|
max: null,
|
||||||
|
last_reset: "1970-01-01T00:00:00+00:00",
|
||||||
|
state: 60,
|
||||||
|
sum: 60,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.energy_consumption_tarif_1",
|
||||||
|
start: "2021-08-03T12:00:00.000Z",
|
||||||
|
mean: null,
|
||||||
|
min: null,
|
||||||
|
max: null,
|
||||||
|
last_reset: "1970-01-01T00:00:00+00:00",
|
||||||
|
state: 70,
|
||||||
|
sum: 70,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.energy_consumption_tarif_1",
|
||||||
|
start: "2021-08-03T13:00:00.000Z",
|
||||||
|
mean: null,
|
||||||
|
min: null,
|
||||||
|
max: null,
|
||||||
|
last_reset: "1970-01-01T00:00:00+00:00",
|
||||||
|
state: 80,
|
||||||
|
sum: 80,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.energy_consumption_tarif_1",
|
||||||
|
start: "2021-08-03T14:00:00.000Z",
|
||||||
|
mean: null,
|
||||||
|
min: null,
|
||||||
|
max: null,
|
||||||
|
last_reset: "1970-01-01T00:00:00+00:00",
|
||||||
|
state: 90,
|
||||||
|
sum: 90,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.energy_consumption_tarif_1",
|
||||||
|
start: "2021-08-03T15:00:00.000Z",
|
||||||
|
mean: null,
|
||||||
|
min: null,
|
||||||
|
max: null,
|
||||||
|
last_reset: "1970-01-01T00:00:00+00:00",
|
||||||
|
state: 100,
|
||||||
|
sum: 100,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.energy_consumption_tarif_2",
|
||||||
|
start: "2021-08-03T15:00:00.000Z",
|
||||||
|
mean: null,
|
||||||
|
min: null,
|
||||||
|
max: null,
|
||||||
|
last_reset: "1970-01-01T00:00:00+00:00",
|
||||||
|
state: 10,
|
||||||
|
sum: 10,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
statistic_id: "sensor.energy_consumption_tarif_2",
|
||||||
|
start: "2021-08-03T16:00:00.000Z",
|
||||||
|
mean: null,
|
||||||
|
min: null,
|
||||||
|
max: null,
|
||||||
|
last_reset: "1970-01-01T00:00:00+00:00",
|
||||||
|
state: 20,
|
||||||
|
sum: 20,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
]
|
||||||
|
),
|
||||||
|
65
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user