diff --git a/demo/src/configs/demo-configs.ts b/demo/src/configs/demo-configs.ts index 28176aafa3..07dca811f7 100644 --- a/demo/src/configs/demo-configs.ts +++ b/demo/src/configs/demo-configs.ts @@ -1,5 +1,6 @@ import { MockHomeAssistant } from "../../../src/fake_data/provide_hass"; import { Lovelace } from "../../../src/panels/lovelace/types"; +import { energyEntities } from "../stubs/entities"; import { DemoConfig } from "./types"; export const demoConfigs: Array<() => Promise> = [ @@ -27,6 +28,7 @@ export const setDemoConfig = async ( selectedDemoConfig = confProm; hass.addEntities(config.entities(hass.localize), true); + hass.addEntities(energyEntities()); lovelace.saveConfig(config.lovelace(hass.localize)); hass.mockTheme(config.theme()); }; diff --git a/demo/src/ha-demo.ts b/demo/src/ha-demo.ts index 349968e28d..38d348be22 100644 --- a/demo/src/ha-demo.ts +++ b/demo/src/ha-demo.ts @@ -20,6 +20,10 @@ import { mockShoppingList } from "./stubs/shopping_list"; import { mockSystemLog } from "./stubs/system_log"; import { mockTemplate } from "./stubs/template"; import { mockTranslations } from "./stubs/translations"; +import { mockEnergy } from "./stubs/energy"; +import { mockConfig } from "./stubs/config"; +import { energyEntities } from "./stubs/entities"; +import { mockForecastSolar } from "./stubs/forecast_solar"; class HaDemo extends HomeAssistantAppEl { protected async _initializeHass() { @@ -47,8 +51,13 @@ class HaDemo extends HomeAssistantAppEl { mockEvents(hass); mockMediaPlayer(hass); mockFrontend(hass); + mockEnergy(hass); + mockForecastSolar(hass); + mockConfig(hass); mockPersistentNotification(hass); + hass.addEntities(energyEntities()); + // Once config is loaded AND localize, set entities and apply theme. Promise.all([selectedDemoConfig, localizePromise]).then( ([conf, localize]) => { diff --git a/demo/src/stubs/config.ts b/demo/src/stubs/config.ts new file mode 100644 index 0000000000..c51700fb1d --- /dev/null +++ b/demo/src/stubs/config.ts @@ -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", + }, + ]); +}; diff --git a/demo/src/stubs/energy.ts b/demo/src/stubs/energy.ts new file mode 100644 index 0000000000..58b7768b61 --- /dev/null +++ b/demo/src/stubs/energy.ts @@ -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: [] })); +}; diff --git a/demo/src/stubs/entities.ts b/demo/src/stubs/entities.ts new file mode 100644 index 0000000000..6ebfae96dc --- /dev/null +++ b/demo/src/stubs/entities.ts @@ -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", + }, + }, + }); diff --git a/demo/src/stubs/forecast_solar.ts b/demo/src/stubs/forecast_solar.ts new file mode 100644 index 0000000000..19bb61083a --- /dev/null +++ b/demo/src/stubs/forecast_solar.ts @@ -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 => ({ + 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, + }, + }, + }) + ); +}; diff --git a/demo/src/stubs/history.ts b/demo/src/stubs/history.ts index 5b96b77ddf..f5000a70a3 100644 --- a/demo/src/stubs/history.ts +++ b/demo/src/stubs/history.ts @@ -1,4 +1,6 @@ +import { addHours, differenceInHours } from "date-fns"; import { HassEntity } from "home-assistant-js-websocket"; +import { StatisticValue } from "../../../src/data/history"; import { MockHomeAssistant } from "../../../src/fake_data/provide_hass"; interface HistoryQueryParams { @@ -64,6 +66,211 @@ const generateHistory = (state, deltas) => { 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) => { mockHass.mockAPI( new RegExp("history/period/.+"), @@ -133,4 +340,39 @@ export const mockHistory = (mockHass: MockHomeAssistant) => { 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 = {}; + + 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; + } + ); }; diff --git a/demo/src/stubs/template.ts b/demo/src/stubs/template.ts index 0e3c1a2638..fc47a08393 100644 --- a/demo/src/stubs/template.ts +++ b/demo/src/stubs/template.ts @@ -6,7 +6,7 @@ export const mockTemplate = (hass: MockHomeAssistant) => { 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!({ result: msg.template, listeners: { all: false, domains: [], entities: [], time: false }, diff --git a/src/data/energy.ts b/src/data/energy.ts index 4bb9611b04..74bec17db0 100644 --- a/src/data/energy.ts +++ b/src/data/energy.ts @@ -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 - return { + const data = { start, end, info, @@ -237,6 +237,8 @@ const getEnergyData = async ( co2SignalConfigEntry, co2SignalEntity, }; + + return data; }; export interface EnergyCollection extends Collection { diff --git a/src/data/entity_registry.ts b/src/data/entity_registry.ts index 0bf6a5b253..28f389922e 100644 --- a/src/data/entity_registry.ts +++ b/src/data/entity_registry.ts @@ -1,4 +1,5 @@ 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 { debounce } from "../common/util/debounce"; import { HomeAssistant } from "../types"; @@ -96,12 +97,15 @@ export const removeEntityRegistryEntry = ( entity_id: entityId, }); -export const fetchEntityRegistry = (conn) => - conn.sendMessagePromise({ +export const fetchEntityRegistry = (conn: Connection) => + conn.sendMessagePromise({ type: "config/entity_registry/list", }); -const subscribeEntityRegistryUpdates = (conn, store) => +const subscribeEntityRegistryUpdates = ( + conn: Connection, + store: Store +) => conn.subscribeEvents( debounce( () => diff --git a/src/fake_data/demo_config.ts b/src/fake_data/demo_config.ts index 91052868ea..b1a4d9837f 100644 --- a/src/fake_data/demo_config.ts +++ b/src/fake_data/demo_config.ts @@ -11,7 +11,13 @@ export const demoConfig: HassConfig = { temperature: "°C", volume: "L", }, - components: ["notify.html5", "history", "shopping_list"], + components: [ + "notify.html5", + "history", + "shopping_list", + "forecast_solar", + "energy", + ], time_zone: "America/Los_Angeles", config_dir: "/config", version: "DEMO", diff --git a/src/fake_data/demo_panels.ts b/src/fake_data/demo_panels.ts index 71aa6ac0ff..319aa49978 100644 --- a/src/fake_data/demo_panels.ts +++ b/src/fake_data/demo_panels.ts @@ -72,6 +72,13 @@ export const demoPanels: Panels = { config: null, url_path: "map", }, + energy: { + component_name: "energy", + icon: "hass:lightning-bolt", + title: "energy", + config: null, + url_path: "energy", + }, // config: { // component_name: "config", // icon: "hass:cog", diff --git a/src/fake_data/provide_hass.ts b/src/fake_data/provide_hass.ts index d537ffa80e..e90665542c 100644 --- a/src/fake_data/provide_hass.ts +++ b/src/fake_data/provide_hass.ts @@ -33,7 +33,11 @@ export interface MockHomeAssistant extends HomeAssistant { addTranslations(translations: Record, language?: string); mockWS( 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); mockEvent(event); @@ -144,7 +148,7 @@ export const provideHass = ( const callback = wsCommands[msg.type]; if (callback) { - callback(msg); + callback(msg, hass()); } else { // eslint-disable-next-line console.error(`Unknown WS command: ${msg.type}`); @@ -153,7 +157,7 @@ export const provideHass = ( sendMessagePromise: async (msg) => { const callback = wsCommands[msg.type]; return callback - ? callback(msg) + ? callback(msg, hass()) : Promise.reject({ code: "command_not_mocked", message: `WS Command ${msg.type} is not implemented in provide_hass.`, @@ -162,7 +166,7 @@ export const provideHass = ( subscribeMessage: async (onChange, msg) => { const callback = wsCommands[msg.type]; return callback - ? callback(msg, onChange) + ? callback(msg, hass(), onChange) : Promise.reject({ code: "command_not_mocked", message: `WS Command ${msg.type} is not implemented in provide_hass.`, @@ -266,6 +270,7 @@ export const provideHass = ( updateStates, updateTranslations, addTranslations, + loadFragmentTranslation: async (_fragment: string) => hass().localize, addEntities, mockWS(type, callback) { wsCommands[type] = callback; diff --git a/src/panels/lovelace/cards/energy/hui-energy-devices-graph-card.ts b/src/panels/lovelace/cards/energy/hui-energy-devices-graph-card.ts index 4451fa0468..3fafe1ad12 100644 --- a/src/panels/lovelace/cards/energy/hui-energy-devices-graph-card.ts +++ b/src/panels/lovelace/cards/energy/hui-energy-devices-graph-card.ts @@ -153,7 +153,7 @@ export class HuiEnergyDevicesGraphCard endTime = new Date( Math.max( ...statisticsData.map((stats) => - new Date(stats[stats.length - 1].start).getTime() + stats.length ? new Date(stats[stats.length - 1].start).getTime() : 0 ) ) ); diff --git a/src/panels/lovelace/cards/energy/hui-energy-distribution-card.ts b/src/panels/lovelace/cards/energy/hui-energy-distribution-card.ts index 3fef716026..6e233c50f7 100644 --- a/src/panels/lovelace/cards/energy/hui-energy-distribution-card.ts +++ b/src/panels/lovelace/cards/energy/hui-energy-distribution-card.ts @@ -10,7 +10,6 @@ import { UnsubscribeFunc } from "home-assistant-js-websocket"; import { css, html, LitElement, svg } from "lit"; import { customElement, property, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; -import { ifDefined } from "lit/directives/if-defined"; import "@material/mwc-button"; import { formatNumber } from "../../../../common/string/format_number"; import "../../../../components/ha-card"; @@ -122,7 +121,8 @@ class HuiEnergyDistrubutionCard let homeLowCarbonCircumference: number | undefined; let homeHighCarbonCircumference: number | undefined; - let electricityMapUrl: string | undefined; + // This fallback is used in the demo + let electricityMapUrl = "https://www.electricitymap.org"; if ( this._data.co2SignalEntity && @@ -140,8 +140,8 @@ class HuiEnergyDistrubutionCard const co2State = this.hass.states[this._data.co2SignalEntity]; - if (co2State) { - electricityMapUrl = `https://www.electricitymap.org/zone/${co2State.attributes.country_code}`; + if (co2State?.attributes.country_code) { + electricityMapUrl += `/zone/${co2State.attributes.country_code}`; } if (highCarbonConsumption !== null) { @@ -168,7 +168,7 @@ class HuiEnergyDistrubutionCard Non-fossil diff --git a/src/panels/lovelace/cards/energy/hui-energy-grid-neutrality-gauge-card.ts b/src/panels/lovelace/cards/energy/hui-energy-grid-neutrality-gauge-card.ts index dd68c567ea..00609e178b 100644 --- a/src/panels/lovelace/cards/energy/hui-energy-grid-neutrality-gauge-card.ts +++ b/src/panels/lovelace/cards/energy/hui-energy-grid-neutrality-gauge-card.ts @@ -98,10 +98,11 @@ class HuiEnergyGridGaugeCard - This card represents your energy dependency. If it's green, it means - you produced more energy than that you 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. + This card represents your energy dependency. +

+ If it's green, it means you produced more energy than that you + 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.
${value !== undefined ? html` 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 - than you consume, try to conserve this energy by installing a battery - or buying an electric car to charge. + home and was not returned to the grid. +

+ If you frequently produce more than you consume, try to conserve this + energy by installing a battery or buying an electric car to charge.
${value !== undefined ? html` - new Date(stats[stats.length - 1].start).getTime() + stats.length ? new Date(stats[stats.length - 1].start).getTime() : 0 ) ) ); diff --git a/src/panels/lovelace/cards/energy/hui-energy-usage-graph-card.ts b/src/panels/lovelace/cards/energy/hui-energy-usage-graph-card.ts index a8e188bed5..a87bfdc526 100644 --- a/src/panels/lovelace/cards/energy/hui-energy-usage-graph-card.ts +++ b/src/panels/lovelace/cards/energy/hui-energy-usage-graph-card.ts @@ -243,7 +243,7 @@ export class HuiEnergyUsageGraphCard endTime = new Date( Math.max( ...statisticsData.map((stats) => - new Date(stats[stats.length - 1].start).getTime() + stats.length ? new Date(stats[stats.length - 1].start).getTime() : 0 ) ) );