Energy demo (#9698)

Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
This commit is contained in:
Bram Kragten 2021-08-03 18:50:31 +02:00 committed by GitHub
parent 9b33ead8aa
commit b246502cb6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 613 additions and 25 deletions

View File

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

View File

@ -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
View 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
View 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
View 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",
},
},
});

View 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,
},
},
})
);
};

View File

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

View File

@ -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 },

View File

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

View File

@ -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(
() => () =>

View File

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

View File

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

View File

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

View File

@ -153,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
) )
) )
); );

View File

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

View File

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

View File

@ -89,9 +89,10 @@ class HuiEnergySolarGaugeCard
<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 used by your This card represents how much of the solar energy was used by your
home and was not returned to the grid. If you frequently produce more home and was not returned to the grid.
than you consume, try to conserve this energy by installing a battery <br /><br />
or buying an electric 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

View File

@ -202,7 +202,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
) )
) )
); );

View File

@ -243,7 +243,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
) )
) )
); );