From af93ec1b92cc57464c450ebc88a83c7e7df9f125 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 5 Oct 2022 10:45:39 +0200 Subject: [PATCH 1/6] Fix zwave config flow (#14001) --- src/panels/config/integrations/ha-domain-integrations.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/panels/config/integrations/ha-domain-integrations.ts b/src/panels/config/integrations/ha-domain-integrations.ts index 45a2716ac0..8df892a620 100644 --- a/src/panels/config/integrations/ha-domain-integrations.ts +++ b/src/panels/config/integrations/ha-domain-integrations.ts @@ -207,10 +207,10 @@ class HaDomainIntegrations extends LitElement { if ( (domain === this.domain && - !this.integration!.config_flow && - (!this.integration!.integrations?.[domain] || - !this.integration!.integrations[domain].config_flow)) || - !this.integration!.integrations?.[domain]?.config_flow + (!this.integration!.integrations || + !(domain in this.integration!.integrations)) && + !this.integration!.config_flow) || + this.integration!.integrations?.[domain]?.config_flow === false ) { const manifest = await fetchIntegrationManifest(this.hass, domain); showAlertDialog(this, { From ef9643ddaf85ff2617f5f619d164251228392351 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 5 Oct 2022 11:03:49 +0200 Subject: [PATCH 2/6] Fix energy demo (#14002) --- demo/src/ha-demo.ts | 8 + demo/src/stubs/energy.ts | 172 +++++++++-------- demo/src/stubs/history.ts | 372 ----------------------------------- demo/src/stubs/recorder.ts | 385 +++++++++++++++++++++++++++++++++++++ src/data/energy.ts | 2 +- 5 files changed, 488 insertions(+), 451 deletions(-) create mode 100644 demo/src/stubs/recorder.ts diff --git a/demo/src/ha-demo.ts b/demo/src/ha-demo.ts index b80c4d71de..070fea8b44 100644 --- a/demo/src/ha-demo.ts +++ b/demo/src/ha-demo.ts @@ -20,6 +20,7 @@ import { mockHistory } from "./stubs/history"; import { mockLovelace } from "./stubs/lovelace"; import { mockMediaPlayer } from "./stubs/media_player"; import { mockPersistentNotification } from "./stubs/persistent_notification"; +import { mockRecorder } from "./stubs/recorder"; import { mockShoppingList } from "./stubs/shopping_list"; import { mockSystemLog } from "./stubs/system_log"; import { mockTemplate } from "./stubs/template"; @@ -45,6 +46,7 @@ class HaDemo extends HomeAssistantAppEl { mockAuth(hass); mockTranslations(hass); mockHistory(hass); + mockRecorder(hass); mockShoppingList(hass); mockSystemLog(hass); mockTemplate(hass); @@ -120,3 +122,9 @@ class HaDemo extends HomeAssistantAppEl { } customElements.define("ha-demo", HaDemo); + +declare global { + interface HTMLElementTagNameMap { + "ha-demo": HaDemo; + } +} diff --git a/demo/src/stubs/energy.ts b/demo/src/stubs/energy.ts index 80f10b968c..b459a9f95c 100644 --- a/demo/src/stubs/energy.ts +++ b/demo/src/stubs/energy.ts @@ -1,85 +1,101 @@ import { format, startOfToday, startOfTomorrow } from "date-fns/esm"; -import { EnergySolarForecasts } from "../../../src/data/energy"; +import { + EnergyInfo, + EnergyPreferences, + EnergySolarForecasts, + FossilEnergyConsumption, +} from "../../../src/data/energy"; 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_price: null, - number_energy_price: null, - }, - { - stat_energy_from: "sensor.energy_consumption_tarif_2", - stat_cost: "sensor.energy_consumption_tarif_2_cost", - 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_price: null, - number_energy_price: null, - }, - { - stat_energy_to: "sensor.energy_production_tarif_2", - stat_compensation: "sensor.energy_production_tarif_2_compensation", - 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"], - }, - /* { - type: "battery", - stat_energy_from: "sensor.battery_output", - stat_energy_to: "sensor.battery_input", - }, */ - { - type: "gas", - stat_energy_from: "sensor.energy_gas", - stat_cost: "sensor.energy_gas_cost", - entity_energy_price: null, - number_energy_price: null, - }, - ], - 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: [] })); - hass.mockWS("energy/fossil_energy_consumption", ({ period }) => ({ - start: period === "month" ? 250 : period === "day" ? 10 : 2, - })); + hass.mockWS( + "energy/get_prefs", + (): EnergyPreferences => ({ + energy_sources: [ + { + type: "grid", + flow_from: [ + { + stat_energy_from: "sensor.energy_consumption_tarif_1", + stat_cost: "sensor.energy_consumption_tarif_1_cost", + 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_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_price: null, + number_energy_price: null, + }, + { + stat_energy_to: "sensor.energy_production_tarif_2", + stat_compensation: + "sensor.energy_production_tarif_2_compensation", + 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"], + }, + /* { + type: "battery", + stat_energy_from: "sensor.battery_output", + stat_energy_to: "sensor.battery_input", + }, */ + { + type: "gas", + stat_energy_from: "sensor.energy_gas", + stat_cost: "sensor.energy_gas_cost", + entity_energy_price: null, + number_energy_price: null, + }, + ], + 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", + (): EnergyInfo => ({ cost_sensors: {}, solar_forecast_domains: [] }) + ); + hass.mockWS( + "energy/fossil_energy_consumption", + ({ period }): FossilEnergyConsumption => ({ + start: period === "month" ? 250 : period === "day" ? 10 : 2, + }) + ); const todayString = format(startOfToday(), "yyyy-MM-dd"); const tomorrowString = format(startOfTomorrow(), "yyyy-MM-dd"); hass.mockWS( diff --git a/demo/src/stubs/history.ts b/demo/src/stubs/history.ts index 86f3445956..5b96b77ddf 100644 --- a/demo/src/stubs/history.ts +++ b/demo/src/stubs/history.ts @@ -1,12 +1,4 @@ -import { - addDays, - addHours, - addMonths, - differenceInHours, - endOfDay, -} from "date-fns/esm"; import { HassEntity } from "home-assistant-js-websocket"; -import { StatisticValue } from "../../../src/data/recorder"; import { MockHomeAssistant } from "../../../src/fake_data/provide_hass"; interface HistoryQueryParams { @@ -72,331 +64,6 @@ const generateHistory = (state, deltas) => { const incrementalUnits = ["clients", "queries", "ads"]; -const generateMeanStatistics = ( - id: string, - start: Date, - end: Date, - period: "5minute" | "hour" | "day" | "month" = "hour", - 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(), - end: currentDate.toISOString(), - mean, - min: mean - Math.random() * maxDiff, - max: mean + Math.random() * maxDiff, - last_reset: "1970-01-01T00:00:00+00:00", - state: mean, - sum: null, - }); - lastVal = mean; - currentDate = - period === "day" - ? addDays(currentDate, 1) - : period === "month" - ? addMonths(currentDate, 1) - : addHours(currentDate, 1); - } - return statistics; -}; - -const generateSumStatistics = ( - id: string, - start: Date, - end: Date, - period: "5minute" | "hour" | "day" | "month" = "hour", - 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(), - end: currentDate.toISOString(), - mean: null, - min: null, - max: null, - last_reset: "1970-01-01T00:00:00+00:00", - state: initValue + sum, - sum, - }); - currentDate = - period === "day" - ? addDays(currentDate, 1) - : period === "month" - ? addMonths(currentDate, 1) - : addHours(currentDate, 1); - } - return statistics; -}; - -const generateCurvedStatistics = ( - id: string, - start: Date, - end: Date, - _period: "5minute" | "hour" | "day" | "month" = "hour", - 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(), - end: 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, - period: "5minute" | "hour" | "day" | "month" - ) => StatisticValue[] -> = { - "sensor.energy_consumption_tarif_1": ( - id: string, - start: Date, - end: Date, - period = "hour" - ) => { - if (period !== "hour") { - return generateSumStatistics( - id, - start, - end, - period, - 0, - period === "day" ? 17 : 504 - ); - } - const morningEnd = new Date(start.getTime() + 10 * 60 * 60 * 1000); - const morningLow = generateSumStatistics( - id, - start, - morningEnd, - period, - 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, - period, - morningFinalVal, - 0 - ); - const eveningLow = generateSumStatistics( - id, - eveningStart, - end, - period, - morningFinalVal, - 0.7 - ); - return [...morningLow, ...empty, ...eveningLow]; - }, - "sensor.energy_consumption_tarif_2": ( - id: string, - start: Date, - end: Date, - period = "hour" - ) => { - if (period !== "hour") { - return generateSumStatistics( - id, - start, - end, - period, - 0, - period === "day" ? 17 : 504 - ); - } - 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, - period, - 0, - 0.3 - ); - const highTarifFinalVal = highTarif.length - ? highTarif[highTarif.length - 1].sum! - : 0; - const morning = generateSumStatistics(id, start, morningEnd, period, 0, 0); - const evening = generateSumStatistics( - id, - eveningStart, - end, - period, - highTarifFinalVal, - 0 - ); - return [...morning, ...highTarif, ...evening]; - }, - "sensor.energy_production_tarif_1": (id, start, end, period = "hour") => - generateSumStatistics(id, start, end, period, 0, 0), - "sensor.energy_production_tarif_1_compensation": ( - id, - start, - end, - period = "hour" - ) => generateSumStatistics(id, start, end, period, 0, 0), - "sensor.energy_production_tarif_2": (id, start, end, period = "hour") => { - if (period !== "hour") { - return generateSumStatistics( - id, - start, - end, - period, - 0, - period === "day" ? 17 : 504 - ); - } - const productionStart = new Date(start.getTime() + 9 * 60 * 60 * 1000); - const productionEnd = new Date(start.getTime() + 21 * 60 * 60 * 1000); - const dayEnd = new Date(endOfDay(productionEnd)); - const production = generateCurvedStatistics( - id, - productionStart, - productionEnd, - period, - 0, - 0.15, - true - ); - const productionFinalVal = production.length - ? production[production.length - 1].sum! - : 0; - const morning = generateSumStatistics( - id, - start, - productionStart, - period, - 0, - 0 - ); - const evening = generateSumStatistics( - id, - productionEnd, - dayEnd, - period, - productionFinalVal, - 0 - ); - const rest = generateSumStatistics( - id, - dayEnd, - end, - period, - productionFinalVal, - 1 - ); - return [...morning, ...production, ...evening, ...rest]; - }, - "sensor.solar_production": (id, start, end, period = "hour") => { - if (period !== "hour") { - return generateSumStatistics( - id, - start, - end, - period, - 0, - period === "day" ? 17 : 504 - ); - } - const productionStart = new Date(start.getTime() + 7 * 60 * 60 * 1000); - const productionEnd = new Date(start.getTime() + 23 * 60 * 60 * 1000); - const dayEnd = new Date(endOfDay(productionEnd)); - const production = generateCurvedStatistics( - id, - productionStart, - productionEnd, - period, - 0, - 0.3, - true - ); - const productionFinalVal = production.length - ? production[production.length - 1].sum! - : 0; - const morning = generateSumStatistics( - id, - start, - productionStart, - period, - 0, - 0 - ); - const evening = generateSumStatistics( - id, - productionEnd, - dayEnd, - period, - productionFinalVal, - 0 - ); - const rest = generateSumStatistics( - id, - dayEnd, - end, - period, - productionFinalVal, - 2 - ); - return [...morning, ...production, ...evening, ...rest]; - }, -}; - export const mockHistory = (mockHass: MockHomeAssistant) => { mockHass.mockAPI( new RegExp("history/period/.+"), @@ -466,43 +133,4 @@ export const mockHistory = (mockHass: MockHomeAssistant) => { return results; } ); - mockHass.mockWS("recorder/get_statistics_metadata", () => []); - mockHass.mockWS("history/list_statistic_ids", () => []); - mockHass.mockWS( - "history/statistics_during_period", - ({ statistic_ids, start_time, end_time, period }, hass) => { - const start = new Date(start_time); - const end = end_time ? new Date(end_time) : new Date(); - - const statistics: Record = {}; - - statistic_ids.forEach((id: string) => { - if (id in statisticsFunctions) { - statistics[id] = statisticsFunctions[id](id, start, end, period); - } 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, - period, - state, - state * (state > 80 ? 0.01 : 0.05) - ) - : generateMeanStatistics( - id, - start, - end, - period, - state, - state * (state > 80 ? 0.05 : 0.1) - ); - } - }); - return statistics; - } - ); }; diff --git a/demo/src/stubs/recorder.ts b/demo/src/stubs/recorder.ts new file mode 100644 index 0000000000..9472772ce1 --- /dev/null +++ b/demo/src/stubs/recorder.ts @@ -0,0 +1,385 @@ +import { + addDays, + addHours, + addMonths, + differenceInHours, + endOfDay, +} from "date-fns"; +import { + Statistics, + StatisticsMetaData, + StatisticValue, +} from "../../../src/data/recorder"; +import { MockHomeAssistant } from "../../../src/fake_data/provide_hass"; + +const generateMeanStatistics = ( + id: string, + start: Date, + end: Date, + period: "5minute" | "hour" | "day" | "month" = "hour", + initValue: number, + maxDiff: number +): StatisticValue[] => { + 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(), + end: currentDate.toISOString(), + mean, + min: mean - Math.random() * maxDiff, + max: mean + Math.random() * maxDiff, + last_reset: "1970-01-01T00:00:00+00:00", + state: mean, + sum: null, + }); + lastVal = mean; + currentDate = + period === "day" + ? addDays(currentDate, 1) + : period === "month" + ? addMonths(currentDate, 1) + : addHours(currentDate, 1); + } + return statistics; +}; + +const generateSumStatistics = ( + id: string, + start: Date, + end: Date, + period: "5minute" | "hour" | "day" | "month" = "hour", + initValue: number, + maxDiff: number +): StatisticValue[] => { + 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(), + end: currentDate.toISOString(), + mean: null, + min: null, + max: null, + last_reset: "1970-01-01T00:00:00+00:00", + state: initValue + sum, + sum, + }); + currentDate = + period === "day" + ? addDays(currentDate, 1) + : period === "month" + ? addMonths(currentDate, 1) + : addHours(currentDate, 1); + } + return statistics; +}; + +const generateCurvedStatistics = ( + id: string, + start: Date, + end: Date, + _period: "5minute" | "hour" | "day" | "month" = "hour", + initValue: number, + maxDiff: number, + metered: boolean +): StatisticValue[] => { + 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(), + end: 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, + period: "5minute" | "hour" | "day" | "month" + ) => StatisticValue[] +> = { + "sensor.energy_consumption_tarif_1": ( + id: string, + start: Date, + end: Date, + period = "hour" + ) => { + if (period !== "hour") { + return generateSumStatistics( + id, + start, + end, + period, + 0, + period === "day" ? 17 : 504 + ); + } + const morningEnd = new Date(start.getTime() + 10 * 60 * 60 * 1000); + const morningLow = generateSumStatistics( + id, + start, + morningEnd, + period, + 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, + period, + morningFinalVal, + 0 + ); + const eveningLow = generateSumStatistics( + id, + eveningStart, + end, + period, + morningFinalVal, + 0.7 + ); + return [...morningLow, ...empty, ...eveningLow]; + }, + "sensor.energy_consumption_tarif_2": ( + id: string, + start: Date, + end: Date, + period = "hour" + ) => { + if (period !== "hour") { + return generateSumStatistics( + id, + start, + end, + period, + 0, + period === "day" ? 17 : 504 + ); + } + 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, + period, + 0, + 0.3 + ); + const highTarifFinalVal = highTarif.length + ? highTarif[highTarif.length - 1].sum! + : 0; + const morning = generateSumStatistics(id, start, morningEnd, period, 0, 0); + const evening = generateSumStatistics( + id, + eveningStart, + end, + period, + highTarifFinalVal, + 0 + ); + return [...morning, ...highTarif, ...evening]; + }, + "sensor.energy_production_tarif_1": (id, start, end, period = "hour") => + generateSumStatistics(id, start, end, period, 0, 0), + "sensor.energy_production_tarif_1_compensation": ( + id, + start, + end, + period = "hour" + ) => generateSumStatistics(id, start, end, period, 0, 0), + "sensor.energy_production_tarif_2": (id, start, end, period = "hour") => { + if (period !== "hour") { + return generateSumStatistics( + id, + start, + end, + period, + 0, + period === "day" ? 17 : 504 + ); + } + const productionStart = new Date(start.getTime() + 9 * 60 * 60 * 1000); + const productionEnd = new Date(start.getTime() + 21 * 60 * 60 * 1000); + const dayEnd = new Date(endOfDay(productionEnd)); + const production = generateCurvedStatistics( + id, + productionStart, + productionEnd, + period, + 0, + 0.15, + true + ); + const productionFinalVal = production.length + ? production[production.length - 1].sum! + : 0; + const morning = generateSumStatistics( + id, + start, + productionStart, + period, + 0, + 0 + ); + const evening = generateSumStatistics( + id, + productionEnd, + dayEnd, + period, + productionFinalVal, + 0 + ); + const rest = generateSumStatistics( + id, + dayEnd, + end, + period, + productionFinalVal, + 1 + ); + return [...morning, ...production, ...evening, ...rest]; + }, + "sensor.solar_production": (id, start, end, period = "hour") => { + if (period !== "hour") { + return generateSumStatistics( + id, + start, + end, + period, + 0, + period === "day" ? 17 : 504 + ); + } + const productionStart = new Date(start.getTime() + 7 * 60 * 60 * 1000); + const productionEnd = new Date(start.getTime() + 23 * 60 * 60 * 1000); + const dayEnd = new Date(endOfDay(productionEnd)); + const production = generateCurvedStatistics( + id, + productionStart, + productionEnd, + period, + 0, + 0.3, + true + ); + const productionFinalVal = production.length + ? production[production.length - 1].sum! + : 0; + const morning = generateSumStatistics( + id, + start, + productionStart, + period, + 0, + 0 + ); + const evening = generateSumStatistics( + id, + productionEnd, + dayEnd, + period, + productionFinalVal, + 0 + ); + const rest = generateSumStatistics( + id, + dayEnd, + end, + period, + productionFinalVal, + 2 + ); + return [...morning, ...production, ...evening, ...rest]; + }, +}; +export const mockRecorder = (mockHass: MockHomeAssistant) => { + mockHass.mockWS( + "recorder/get_statistics_metadata", + (): StatisticsMetaData[] => [] + ); + mockHass.mockWS( + "recorder/list_statistic_ids", + (): StatisticsMetaData[] => [] + ); + mockHass.mockWS( + "recorder/statistics_during_period", + ({ statistic_ids, start_time, end_time, period }, hass): Statistics => { + const start = new Date(start_time); + const end = end_time ? new Date(end_time) : new Date(); + + const statistics: Record = {}; + + statistic_ids.forEach((id: string) => { + if (id in statisticsFunctions) { + statistics[id] = statisticsFunctions[id](id, start, end, period); + } 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, + period, + state, + state * (state > 80 ? 0.01 : 0.05) + ) + : generateMeanStatistics( + id, + start, + end, + period, + state, + state * (state > 80 ? 0.05 : 0.1) + ); + } + }); + return statistics; + } + ); +}; diff --git a/src/data/energy.ts b/src/data/energy.ts index 80dcfbb511..be4dafffe0 100644 --- a/src/data/energy.ts +++ b/src/data/energy.ts @@ -618,7 +618,7 @@ export const getEnergyGasUnitClass = ( const statisticIdWithMeta = statisticsMetaData[source.stat_energy_from]; if ( energyGasUnitClass.includes( - statisticIdWithMeta.unit_class as EnergyGasUnitClass + statisticIdWithMeta?.unit_class as EnergyGasUnitClass ) ) { return statisticIdWithMeta.unit_class as EnergyGasUnitClass; From 83a4fd6c1bc30ced4a8bec631c9fc42f27913984 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Wed, 5 Oct 2022 12:12:28 +0200 Subject: [PATCH 3/6] Improve explanation of configuration validation (#14000) --- src/translations/en.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/translations/en.json b/src/translations/en.json index 4494ffae8a..af79aefb5c 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -4594,10 +4594,10 @@ "title": "YAML", "section": { "validation": { - "heading": "Configuration validation", - "introduction": "Validate your configuration if you recently made some changes to it and want to make sure that it is all valid.", + "heading": "Check and Restart", + "introduction": "A basic validation of the configuration is automatically done before restarting. The basic validation ensures the YAML configuration doesn't have errors which will prevent Home Assistant or any integration from starting. It's also possible to only do the basic validation check without restarting.", "check_config": "Check configuration", - "valid": "Configuration valid!", + "valid": "Configuration will not prevent Home Assistant from starting!", "invalid": "Configuration invalid!" }, "reloading": { From aaeca323d4f1b7073072b8c4863356b053057688 Mon Sep 17 00:00:00 2001 From: karliemeads <68717336+karliemeads@users.noreply.github.com> Date: Wed, 5 Oct 2022 09:01:43 -0400 Subject: [PATCH 4/6] Highlight focus on show/hide password toggle (#13997) --- src/components/ha-selector/ha-selector-text.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/ha-selector/ha-selector-text.ts b/src/components/ha-selector/ha-selector-text.ts index 7af6c36d27..dd47b2c27c 100644 --- a/src/components/ha-selector/ha-selector-text.ts +++ b/src/components/ha-selector/ha-selector-text.ts @@ -98,13 +98,13 @@ export class HaTextSelector extends LitElement { } ha-icon-button { position: absolute; - top: 16px; - right: 16px; - --mdc-icon-button-size: 24px; + top: 10px; + right: 10px; + --mdc-icon-button-size: 36px; --mdc-icon-size: 20px; color: var(--secondary-text-color); inset-inline-start: initial; - inset-inline-end: 16px; + inset-inline-end: 10px; direction: var(--direction); } `; From 1ac701d1c8361b6142c8b63448f419cedb06dc53 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 5 Oct 2022 16:26:06 +0200 Subject: [PATCH 5/6] change yaml integration dialog (#14003) * change yaml integration dialog * Update dialog-yaml-integration.ts * Update src/panels/config/integrations/dialog-yaml-integration.ts Co-authored-by: Paul Bottein Co-authored-by: Paul Bottein --- .../integrations/dialog-add-integration.ts | 36 +------ .../integrations/dialog-yaml-integration.ts | 96 +++++++++++++++++++ .../integrations/ha-domain-integrations.ts | 69 +++++-------- .../show-add-integration-dialog.ts | 16 ++++ src/translations/en.json | 4 +- 5 files changed, 145 insertions(+), 76 deletions(-) create mode 100644 src/panels/config/integrations/dialog-yaml-integration.ts diff --git a/src/panels/config/integrations/dialog-add-integration.ts b/src/panels/config/integrations/dialog-add-integration.ts index a775b05506..865cabbd5d 100644 --- a/src/panels/config/integrations/dialog-add-integration.ts +++ b/src/panels/config/integrations/dialog-add-integration.ts @@ -36,10 +36,12 @@ import { } from "../../../dialogs/generic/show-dialog-box"; import { haStyleDialog, haStyleScrollbar } from "../../../resources/styles"; import type { HomeAssistant } from "../../../types"; -import { documentationUrl } from "../../../util/documentation-url"; import "./ha-domain-integrations"; import "./ha-integration-list-item"; -import { AddIntegrationDialogParams } from "./show-add-integration-dialog"; +import { + AddIntegrationDialogParams, + showYamlIntegrationDialog, +} from "./show-add-integration-dialog"; export interface IntegrationListItem { name: string; @@ -545,35 +547,7 @@ class AddIntegrationDialog extends LitElement { this.hass, integration.domain ); - showAlertDialog(this, { - title: this.hass.localize( - "ui.panel.config.integrations.config_flow.yaml_only_title" - ), - text: this.hass.localize( - "ui.panel.config.integrations.config_flow.yaml_only_text", - { - link: - manifest?.is_built_in || manifest?.documentation - ? html` - ${this.hass.localize( - "ui.panel.config.integrations.config_flow.documentation" - )}` - : this.hass.localize( - "ui.panel.config.integrations.config_flow.documentation" - ), - } - ), - }); + showYamlIntegrationDialog(this, { manifest }); } private async _createFlow(integration: IntegrationListItem) { diff --git a/src/panels/config/integrations/dialog-yaml-integration.ts b/src/panels/config/integrations/dialog-yaml-integration.ts new file mode 100644 index 0000000000..932c955bde --- /dev/null +++ b/src/panels/config/integrations/dialog-yaml-integration.ts @@ -0,0 +1,96 @@ +import "@material/mwc-button/mwc-button"; +import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import { fireEvent } from "../../../common/dom/fire_event"; +import { HomeAssistant } from "../../../types"; +import { documentationUrl } from "../../../util/documentation-url"; +import { YamlIntegrationDialogParams } from "./show-add-integration-dialog"; + +@customElement("dialog-yaml-integration") +export class DialogYamlIntegration extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @state() private _params?: YamlIntegrationDialogParams; + + public showDialog(params: YamlIntegrationDialogParams): void { + this._params = params; + } + + public closeDialog() { + this._params = undefined; + fireEvent(this, "dialog-closed", { dialog: this.localName }); + } + + protected render(): TemplateResult { + if (!this._params) { + return html``; + } + const manifest = this._params.manifest; + const docLink = manifest.is_built_in + ? documentationUrl(this.hass, `/integrations/${manifest.domain}`) + : manifest.documentation; + return html` + +

+ ${this.hass.localize( + "ui.panel.config.integrations.config_flow.yaml_only" + )} +

+ + ${this.hass.localize("ui.dialogs.generic.cancel")} + + ${docLink + ? html` + + ${this.hass.localize( + "ui.panel.config.integrations.config_flow.open_documentation" + )} + + ` + : html` + ${this.hass.localize("ui.dialogs.generic.ok")} + `} +
+ `; + } + + static get styles(): CSSResultGroup { + return css` + :host([inert]) { + pointer-events: initial !important; + cursor: initial !important; + } + a { + text-decoration: none; + } + ha-dialog { + --mdc-dialog-heading-ink-color: var(--primary-text-color); + --mdc-dialog-content-ink-color: var(--primary-text-color); + /* Place above other dialogs */ + --dialog-z-index: 104; + } + @media all and (min-width: 600px) { + ha-dialog { + --mdc-dialog-min-width: 400px; + } + } + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "dialog-yaml-integration": DialogYamlIntegration; + } +} diff --git a/src/panels/config/integrations/ha-domain-integrations.ts b/src/panels/config/integrations/ha-domain-integrations.ts index 8df892a620..6b9a89e73d 100644 --- a/src/panels/config/integrations/ha-domain-integrations.ts +++ b/src/panels/config/integrations/ha-domain-integrations.ts @@ -1,8 +1,10 @@ +import { RequestSelectedDetail } from "@material/mwc-list/mwc-list-item-base"; import { css, html, LitElement } from "lit"; import { customElement, property } from "lit/decorators"; import { isComponentLoaded } from "../../../common/config/is_component_loaded"; import { fireEvent } from "../../../common/dom/fire_event"; import { protocolIntegrationPicked } from "../../../common/integrations/protocolIntegrationPicked"; +import { shouldHandleRequestSelectedEvent } from "../../../common/mwc/handle-request-selected-event"; import { navigate } from "../../../common/navigate"; import { caseInsensitiveStringCompare } from "../../../common/string/compare"; import { localizeConfigFlowTitle } from "../../../data/config_flow"; @@ -13,12 +15,11 @@ import { } from "../../../data/integration"; import { Integration } from "../../../data/integrations"; import { showConfigFlowDialog } from "../../../dialogs/config-flow/show-dialog-config-flow"; -import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box"; import { haStyle } from "../../../resources/styles"; import { HomeAssistant } from "../../../types"; import { brandsUrl } from "../../../util/brands-url"; -import { documentationUrl } from "../../../util/documentation-url"; import "./ha-integration-list-item"; +import { showYamlIntegrationDialog } from "./show-add-integration-dialog"; const standardToDomain = { zigbee: "zha", zwave: "zwave_js" } as const; @@ -43,7 +44,7 @@ class HaDomainIntegrations extends LitElement { (flow) => html` ` ) @@ -138,7 +139,7 @@ class HaDomainIntegrations extends LitElement { ? html` ${this.hass.localize("ui.panel.config.integrations.new_flow", { @@ -186,15 +187,18 @@ class HaDomainIntegrations extends LitElement { is_built_in: this.integration.is_built_in !== false, cloud: this.integration.iot_class?.startsWith("cloud_"), }} - @click=${this._integrationPicked} + @request-selected=${this._integrationPicked} > `}` : ""} `; } - private async _integrationPicked(ev) { - const domain = ev.currentTarget.domain; + private async _integrationPicked(ev: CustomEvent) { + if (!shouldHandleRequestSelectedEvent(ev)) { + return; + } + const domain = (ev.currentTarget as any).domain; if ( ["cloud", "google_assistant", "alexa"].includes(domain) && @@ -213,37 +217,10 @@ class HaDomainIntegrations extends LitElement { this.integration!.integrations?.[domain]?.config_flow === false ) { const manifest = await fetchIntegrationManifest(this.hass, domain); - showAlertDialog(this, { - title: this.hass.localize( - "ui.panel.config.integrations.config_flow.yaml_only_title" - ), - text: this.hass.localize( - "ui.panel.config.integrations.config_flow.yaml_only_text", - { - link: - manifest?.is_built_in || manifest?.documentation - ? html` - ${this.hass.localize( - "ui.panel.config.integrations.config_flow.documentation" - )}` - : this.hass.localize( - "ui.panel.config.integrations.config_flow.documentation" - ), - } - ), - }); + showYamlIntegrationDialog(this, { manifest }); return; } + const root = this.getRootNode(); showConfigFlowDialog( root instanceof ShadowRoot ? (root.host as HTMLElement) : this, @@ -256,8 +233,11 @@ class HaDomainIntegrations extends LitElement { fireEvent(this, "close-dialog"); } - private async _flowInProgressPicked(ev) { - const flow: DataEntryFlowProgress = ev.currentTarget.flow; + private async _flowInProgressPicked(ev: CustomEvent) { + if (!shouldHandleRequestSelectedEvent(ev)) { + return; + } + const flow: DataEntryFlowProgress = (ev.currentTarget as any).flow; const root = this.getRootNode(); showConfigFlowDialog( root instanceof ShadowRoot ? (root.host as HTMLElement) : this, @@ -270,8 +250,11 @@ class HaDomainIntegrations extends LitElement { fireEvent(this, "close-dialog"); } - private _standardPicked(ev) { - const domain = ev.currentTarget.domain; + private _standardPicked(ev: CustomEvent) { + if (!shouldHandleRequestSelectedEvent(ev)) { + return; + } + const domain = (ev.currentTarget as any).domain; const root = this.getRootNode(); fireEvent(this, "close-dialog"); protocolIntegrationPicked( diff --git a/src/panels/config/integrations/show-add-integration-dialog.ts b/src/panels/config/integrations/show-add-integration-dialog.ts index 1530b73ce6..0bfbf0cb76 100644 --- a/src/panels/config/integrations/show-add-integration-dialog.ts +++ b/src/panels/config/integrations/show-add-integration-dialog.ts @@ -1,10 +1,15 @@ import { fireEvent } from "../../../common/dom/fire_event"; +import { IntegrationManifest } from "../../../data/integration"; export interface AddIntegrationDialogParams { brand?: string; initialFilter?: string; } +export interface YamlIntegrationDialogParams { + manifest: IntegrationManifest; +} + export const showAddIntegrationDialog = ( element: HTMLElement, dialogParams?: AddIntegrationDialogParams @@ -15,3 +20,14 @@ export const showAddIntegrationDialog = ( dialogParams: dialogParams, }); }; + +export const showYamlIntegrationDialog = ( + element: HTMLElement, + dialogParams?: YamlIntegrationDialogParams +): void => { + fireEvent(element, "show-dialog", { + dialogTag: "dialog-yaml-integration", + dialogImport: () => import("./dialog-yaml-integration"), + dialogParams: dialogParams, + }); +}; diff --git a/src/translations/en.json b/src/translations/en.json index af79aefb5c..b2c6fbd067 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -2965,8 +2965,8 @@ "next": "Next", "found_following_devices": "We found the following devices", "yaml_only_title": "This device can not be added from the UI", - "yaml_only_text": "You can add this device by adding it to your `configuration.yaml`. See the {link} for more information.", - "documentation": "documentation", + "yaml_only": "You can add this device by adding it to your ''configuration.yaml''. See the documentation for more information.", + "open_documentation": "Open documentation", "no_config_flow": "This integration does not support configuration via the UI. If you followed this link from the Home Assistant website, make sure you run the latest version of Home Assistant.", "not_all_required_fields": "Not all required fields are filled in.", "error_saving_area": "Error saving area: {error}", From 88983806ae4bb02d2446f03270b579bf253d93ba Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 5 Oct 2022 16:38:36 +0200 Subject: [PATCH 6/6] Bumped version to 20221005.0 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 14b67e7416..b632b56980 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "home-assistant-frontend" -version = "20221004.0" +version = "20221005.0" license = {text = "Apache-2.0"} description = "The Home Assistant frontend" readme = "README.md"