- `${context.dataset.label}: ${context.parsed.y} kWh`,
+ `${context.dataset.label}: ${formatNumber(
+ context.parsed.y,
+ this.hass.locale
+ )} kWh`,
},
},
filler: {
@@ -212,6 +220,8 @@ export class HuiEnergySolarGraphCard
hitRadius: 5,
},
},
+ // @ts-expect-error
+ locale: numberFormatToLocale(this.hass.locale),
};
}
@@ -219,6 +229,7 @@ export class HuiEnergySolarGraphCard
if (this._fetching) {
return;
}
+
const startDate = new Date();
startDate.setHours(0, 0, 0, 0);
startDate.setTime(startDate.getTime() - 1000 * 60 * 60); // subtract 1 hour to get a startpoint
@@ -273,20 +284,25 @@ export class HuiEnergySolarGraphCard
endTime = new Date();
}
+ const computedStyles = getComputedStyle(this);
+ const solarColor = computedStyles
+ .getPropertyValue("--energy-solar-color")
+ .trim();
+
solarSources.forEach((source, idx) => {
const data: ChartDataset<"bar" | "line">[] = [];
const entity = this.hass.states[source.stat_energy_from];
const borderColor =
idx > 0
- ? rgb2hex(lab2rgb(labDarken(rgb2lab(hex2rgb(SOLAR_COLOR)), idx)))
- : SOLAR_COLOR;
+ ? rgb2hex(lab2rgb(labDarken(rgb2lab(hex2rgb(solarColor)), idx)))
+ : solarColor;
data.push({
label: `Production ${
entity ? computeStateName(entity) : source.stat_energy_from
}`,
- borderColor: borderColor,
+ borderColor,
backgroundColor: borderColor + "7F",
data: [],
});
@@ -307,7 +323,7 @@ export class HuiEnergySolarGraphCard
if (prevStart === point.start) {
continue;
}
- const value = Math.round((point.sum - prevValue) * 100) / 100;
+ const value = point.sum - prevValue;
const date = new Date(point.start);
data[0].data.push({
x: date.getTime(),
@@ -347,7 +363,9 @@ export class HuiEnergySolarGraphCard
}`,
fill: false,
stepped: false,
- borderColor: "#000",
+ borderColor: computedStyles.getPropertyValue(
+ "--primary-text-color"
+ ),
borderDash: [7, 5],
pointRadius: 0,
data: [],
@@ -386,6 +404,9 @@ export class HuiEnergySolarGraphCard
ha-card {
height: 100%;
}
+ .card-header {
+ padding-bottom: 0;
+ }
.content {
padding: 16px;
}
diff --git a/src/panels/lovelace/cards/energy/hui-energy-sources-table-card.ts b/src/panels/lovelace/cards/energy/hui-energy-sources-table-card.ts
new file mode 100644
index 0000000000..44f4336609
--- /dev/null
+++ b/src/panels/lovelace/cards/energy/hui-energy-sources-table-card.ts
@@ -0,0 +1,426 @@
+// @ts-ignore
+import dataTableStyles from "@material/data-table/dist/mdc.data-table.min.css";
+import {
+ css,
+ CSSResultGroup,
+ html,
+ LitElement,
+ TemplateResult,
+ unsafeCSS,
+} from "lit";
+import { customElement, property, state } from "lit/decorators";
+import { styleMap } from "lit/directives/style-map";
+import {
+ rgb2hex,
+ lab2rgb,
+ rgb2lab,
+ hex2rgb,
+} from "../../../../common/color/convert-color";
+import { labDarken } from "../../../../common/color/lab";
+import { computeStateName } from "../../../../common/entity/compute_state_name";
+import { formatNumber } from "../../../../common/string/format_number";
+import "../../../../components/chart/statistics-chart";
+import "../../../../components/ha-card";
+import {
+ EnergyInfo,
+ energySourcesByType,
+ getEnergyInfo,
+} from "../../../../data/energy";
+import {
+ calculateStatisticSumGrowth,
+ fetchStatistics,
+ Statistics,
+} from "../../../../data/history";
+import { HomeAssistant } from "../../../../types";
+import { LovelaceCard } from "../../types";
+import { EnergySourcesTableCardConfig } from "../types";
+
+@customElement("hui-energy-sources-table-card")
+export class HuiEnergySourcesTableCard
+ extends LitElement
+ implements LovelaceCard
+{
+ @property({ attribute: false }) public hass!: HomeAssistant;
+
+ @state() private _config?: EnergySourcesTableCardConfig;
+
+ @state() private _stats?: Statistics;
+
+ @state() private _energyInfo?: EnergyInfo;
+
+ public getCardSize(): Promise
| number {
+ return 3;
+ }
+
+ public setConfig(config: EnergySourcesTableCardConfig): void {
+ this._config = config;
+ }
+
+ public willUpdate() {
+ if (!this.hasUpdated) {
+ this._getEnergyInfo().then(() => this._getStatistics());
+ }
+ }
+
+ protected render(): TemplateResult {
+ if (!this.hass || !this._config) {
+ return html``;
+ }
+
+ if (!this._stats) {
+ return html`Loading...`;
+ }
+
+ let totalGrid = 0;
+ let totalSolar = 0;
+ let totalCost = 0;
+
+ const types = energySourcesByType(this._config.prefs);
+
+ const computedStyles = getComputedStyle(this);
+ const solarColor = computedStyles
+ .getPropertyValue("--energy-solar-color")
+ .trim();
+ const returnColor = computedStyles
+ .getPropertyValue("--energy-grid-return-color")
+ .trim();
+ const consumptionColor = computedStyles
+ .getPropertyValue("--energy-grid-consumption-color")
+ .trim();
+
+ const showCosts =
+ types.grid?.[0].flow_from.some(
+ (flow) =>
+ flow.stat_cost || flow.entity_energy_price || flow.number_energy_price
+ ) ||
+ types.grid?.[0].flow_to.some(
+ (flow) =>
+ flow.stat_compensation ||
+ flow.entity_energy_price ||
+ flow.number_energy_price
+ );
+
+ return html`
+ ${this._config.title
+ ? html``
+ : ""}
+
+
+
+
+
+
+
+ ${types.solar?.map((source, idx) => {
+ const entity = this.hass.states[source.stat_energy_from];
+ const energy =
+ calculateStatisticSumGrowth(
+ this._stats![source.stat_energy_from]
+ ) || 0;
+ totalSolar += energy;
+ const color =
+ idx > 0
+ ? rgb2hex(
+ lab2rgb(labDarken(rgb2lab(hex2rgb(solarColor)), idx))
+ )
+ : solarColor;
+ return html`
+
+
+ |
+
+ ${entity
+ ? computeStateName(entity)
+ : source.stat_energy_from}
+ |
+
+ ${formatNumber(energy, this.hass.locale)} kWh
+ |
+ ${showCosts
+ ? html` | `
+ : ""}
+
`;
+ })}
+ ${types.solar
+ ? html`
+ |
+
+ Solar total
+ |
+
+ ${formatNumber(totalSolar, this.hass.locale)} kWh
+ |
+ ${showCosts
+ ? html` | `
+ : ""}
+
`
+ : ""}
+ ${types.grid?.map(
+ (source) => html`${source.flow_from.map((flow, idx) => {
+ const entity = this.hass.states[flow.stat_energy_from];
+ const energy =
+ calculateStatisticSumGrowth(
+ this._stats![flow.stat_energy_from]
+ ) || 0;
+ totalGrid += energy;
+ const cost_stat =
+ flow.stat_cost ||
+ this._energyInfo!.cost_sensors[flow.stat_energy_from];
+ const cost = cost_stat
+ ? calculateStatisticSumGrowth(this._stats![cost_stat])
+ : null;
+ if (cost !== null) {
+ totalCost += cost;
+ }
+ const color =
+ idx > 0
+ ? rgb2hex(
+ lab2rgb(
+ labDarken(rgb2lab(hex2rgb(consumptionColor)), idx)
+ )
+ )
+ : consumptionColor;
+ return html`
+
+
+ |
+
+ ${entity
+ ? computeStateName(entity)
+ : flow.stat_energy_from}
+ |
+
+ ${formatNumber(energy, this.hass.locale)} kWh
+ |
+ ${showCosts
+ ? html`
+ ${cost !== null
+ ? formatNumber(cost, this.hass.locale, {
+ style: "currency",
+ currency: this.hass.config.currency!,
+ })
+ : ""}
+ | `
+ : ""}
+
`;
+ })}
+ ${source.flow_to.map((flow, idx) => {
+ const entity = this.hass.states[flow.stat_energy_to];
+ const energy =
+ (calculateStatisticSumGrowth(
+ this._stats![flow.stat_energy_to]
+ ) || 0) * -1;
+ totalGrid += energy;
+ const cost_stat =
+ flow.stat_compensation ||
+ this._energyInfo!.cost_sensors[flow.stat_energy_to];
+ const cost = cost_stat
+ ? calculateStatisticSumGrowth(this._stats![cost_stat])
+ : null;
+ if (cost !== null) {
+ totalCost += cost;
+ }
+ const color =
+ idx > 0
+ ? rgb2hex(
+ lab2rgb(labDarken(rgb2lab(hex2rgb(returnColor)), idx))
+ )
+ : returnColor;
+ return html`
+
+
+ |
+
+ ${entity ? computeStateName(entity) : flow.stat_energy_to}
+ |
+
+ ${formatNumber(energy, this.hass.locale)} kWh
+ |
+ ${showCosts
+ ? html`
+ ${cost !== null
+ ? formatNumber(cost, this.hass.locale, {
+ style: "currency",
+ currency: this.hass.config.currency!,
+ })
+ : ""}
+ | `
+ : ""}
+
`;
+ })}`
+ )}
+ ${types.grid
+ ? html`
+ |
+ Grid total |
+
+ ${formatNumber(totalGrid, this.hass.locale)} kWh
+ |
+ ${showCosts
+ ? html`
+ ${formatNumber(totalCost, this.hass.locale, {
+ style: "currency",
+ currency: this.hass.config.currency!,
+ })}
+ | `
+ : ""}
+
`
+ : ""}
+
+
+
+
+ `;
+ }
+
+ private async _getEnergyInfo() {
+ this._energyInfo = await getEnergyInfo(this.hass);
+ }
+
+ private async _getStatistics(): Promise {
+ const startDate = new Date();
+ startDate.setHours(0, 0, 0, 0);
+ startDate.setTime(startDate.getTime() - 1000 * 60 * 60); // subtract 1 hour to get a startpoint
+
+ const statistics: string[] = Object.values(this._energyInfo!.cost_sensors);
+ const prefs = this._config!.prefs;
+ for (const source of prefs.energy_sources) {
+ if (source.type === "solar") {
+ statistics.push(source.stat_energy_from);
+ } else {
+ // grid source
+ for (const flowFrom of source.flow_from) {
+ statistics.push(flowFrom.stat_energy_from);
+ if (flowFrom.stat_cost) {
+ statistics.push(flowFrom.stat_cost);
+ }
+ }
+ for (const flowTo of source.flow_to) {
+ statistics.push(flowTo.stat_energy_to);
+ if (flowTo.stat_compensation) {
+ statistics.push(flowTo.stat_compensation);
+ }
+ }
+ }
+ }
+
+ this._stats = await fetchStatistics(
+ this.hass!,
+ startDate,
+ undefined,
+ statistics
+ );
+ }
+
+ static get styles(): CSSResultGroup {
+ return css`
+ ${unsafeCSS(dataTableStyles)}
+ .mdc-data-table {
+ width: 100%;
+ border: 0;
+ }
+ .mdc-data-table__header-cell,
+ .mdc-data-table__cell {
+ color: var(--primary-text-color);
+ border-bottom-color: var(--divider-color);
+ }
+ .mdc-data-table__row:not(.mdc-data-table__row--selected):hover {
+ background-color: rgba(var(--rgb-primary-text-color), 0.04);
+ }
+ .total {
+ --mdc-typography-body2-font-weight: 500;
+ }
+ .total .mdc-data-table__cell {
+ border-top: 1px solid var(--divider-color);
+ }
+ ha-card {
+ height: 100%;
+ }
+ .card-header {
+ padding-bottom: 0;
+ }
+ .content {
+ padding: 16px;
+ }
+ .has-header {
+ padding-top: 0;
+ }
+ .cell-bullet {
+ width: 32px;
+ padding-right: 0;
+ }
+ .bullet {
+ border-width: 1px;
+ border-style: solid;
+ border-radius: 4px;
+ height: 16px;
+ width: 32px;
+ }
+ `;
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "hui-energy-sources-table-card": HuiEnergySourcesTableCard;
+ }
+}
diff --git a/src/panels/lovelace/cards/energy/hui-energy-summary-graph-card.ts b/src/panels/lovelace/cards/energy/hui-energy-usage-graph-card.ts
similarity index 74%
rename from src/panels/lovelace/cards/energy/hui-energy-summary-graph-card.ts
rename to src/panels/lovelace/cards/energy/hui-energy-usage-graph-card.ts
index 2e3eb96354..5c455a4865 100644
--- a/src/panels/lovelace/cards/energy/hui-energy-summary-graph-card.ts
+++ b/src/panels/lovelace/cards/energy/hui-energy-usage-graph-card.ts
@@ -9,39 +9,34 @@ import {
} from "lit";
import { customElement, property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
-import { styleMap } from "lit/directives/style-map";
import {
hex2rgb,
lab2rgb,
rgb2hex,
rgb2lab,
} from "../../../../common/color/convert-color";
+import { hexBlend } from "../../../../common/color/hex";
import { labDarken } from "../../../../common/color/lab";
import { computeStateName } from "../../../../common/entity/compute_state_name";
-import { round } from "../../../../common/number/round";
-import { formatNumber } from "../../../../common/string/format_number";
+import {
+ formatNumber,
+ numberFormatToLocale,
+} from "../../../../common/string/format_number";
import "../../../../components/chart/ha-chart-base";
import "../../../../components/ha-card";
import { fetchStatistics, Statistics } from "../../../../data/history";
import { HomeAssistant } from "../../../../types";
import { LovelaceCard } from "../../types";
-import { EnergySummaryGraphCardConfig } from "../types";
+import { EnergyUsageGraphCardConfig } from "../types";
-const NEGATIVE = ["to_grid"];
-const COLORS = {
- to_grid: { border: "#673ab7", background: "#b39bdb" },
- from_grid: { border: "#126A9A", background: "#8ab5cd" },
- used_solar: { border: "#FF9800", background: "#fecc8e" },
-};
-
-@customElement("hui-energy-summary-graph-card")
-export class HuiEnergySummaryGraphCard
+@customElement("hui-energy-usage-graph-card")
+export class HuiEnergyUsageGraphCard
extends LitElement
implements LovelaceCard
{
@property({ attribute: false }) public hass!: HomeAssistant;
- @state() private _config?: EnergySummaryGraphCardConfig;
+ @state() private _config?: EnergyUsageGraphCardConfig;
@state() private _data?: Statistics;
@@ -81,7 +76,7 @@ export class HuiEnergySummaryGraphCard
return 3;
}
- public setConfig(config: EnergySummaryGraphCardConfig): void {
+ public setConfig(config: EnergyUsageGraphCardConfig): void {
this._config = config;
}
@@ -95,7 +90,7 @@ export class HuiEnergySummaryGraphCard
}
const oldConfig = changedProps.get("_config") as
- | EnergySummaryGraphCardConfig
+ | EnergyUsageGraphCardConfig
| undefined;
if (oldConfig !== this._config) {
@@ -116,42 +111,14 @@ export class HuiEnergySummaryGraphCard
return html`
-
+ ${this._config.title
+ ? html``
+ : ""}
-
-
- ${this._chartData.datasets.map(
- (dataset) => html`-
-
- ${formatNumber(
- Math.abs(
- dataset.data.reduce(
- (total, point) => total + (point as any).y,
- 0
- ) as number
- ),
- this.hass.locale
- )}
- kWh
-
`
- )}
-
-
Math.abs(round(value)),
+ callback: (value) =>
+ formatNumber(Math.abs(value), this.hass.locale),
},
},
},
@@ -218,7 +186,10 @@ export class HuiEnergySummaryGraphCard
filter: (val) => val.formattedValue !== "0",
callbacks: {
label: (context) =>
- `${context.dataset.label}: ${Math.abs(context.parsed.y)} kWh`,
+ `${context.dataset.label}: ${formatNumber(
+ Math.abs(context.parsed.y),
+ this.hass.locale
+ )} kWh`,
footer: (contexts) => {
let totalConsumed = 0;
let totalReturned = 0;
@@ -233,10 +204,16 @@ export class HuiEnergySummaryGraphCard
}
return [
totalConsumed
- ? `Total consumed: ${totalConsumed.toFixed(2)} kWh`
+ ? `Total consumed: ${formatNumber(
+ totalConsumed,
+ this.hass.locale
+ )} kWh`
: "",
totalReturned
- ? `Total returned: ${totalReturned.toFixed(2)} kWh`
+ ? `Total returned: ${formatNumber(
+ totalReturned,
+ this.hass.locale
+ )} kWh`
: "",
].filter(Boolean);
},
@@ -261,6 +238,8 @@ export class HuiEnergySummaryGraphCard
hitRadius: 5,
},
},
+ // @ts-expect-error
+ locale: numberFormatToLocale(this.hass.locale),
};
}
@@ -344,6 +323,23 @@ export class HuiEnergySummaryGraphCard
} = {};
const summedData: { [key: string]: { [start: string]: number } } = {};
+ const computedStyles = getComputedStyle(this);
+ const colors = {
+ to_grid: computedStyles
+ .getPropertyValue("--energy-grid-return-color")
+ .trim(),
+ from_grid: computedStyles
+ .getPropertyValue("--energy-grid-consumption-color")
+ .trim(),
+ used_solar: computedStyles
+ .getPropertyValue("--energy-solar-color")
+ .trim(),
+ };
+
+ const backgroundColor = computedStyles
+ .getPropertyValue("--card-background-color")
+ .trim();
+
Object.entries(statistics).forEach(([key, statIds]) => {
const sum = ["solar", "to_grid"].includes(key);
const add = key !== "solar";
@@ -407,12 +403,13 @@ export class HuiEnergySummaryGraphCard
const uniqueKeys = Array.from(new Set(allKeys));
Object.entries(combinedData).forEach(([type, sources]) => {
- const negative = NEGATIVE.includes(type);
-
Object.entries(sources).forEach(([statId, source], idx) => {
const data: ChartDataset<"bar">[] = [];
const entity = this.hass.states[statId];
- const color = COLORS[type];
+ const borderColor =
+ idx > 0
+ ? rgb2hex(lab2rgb(labDarken(rgb2lab(hex2rgb(colors[type])), idx)))
+ : colors[type];
data.push({
label:
@@ -421,28 +418,20 @@ export class HuiEnergySummaryGraphCard
: entity
? computeStateName(entity)
: statId,
- borderColor:
- idx > 0
- ? rgb2hex(lab2rgb(labDarken(rgb2lab(hex2rgb(color.border)), idx)))
- : color.border,
- backgroundColor:
- idx > 0
- ? rgb2hex(
- lab2rgb(labDarken(rgb2lab(hex2rgb(color.background)), idx))
- )
- : color.background,
+ borderColor,
+ backgroundColor: hexBlend(borderColor, backgroundColor, 50),
stack: "stack",
data: [],
});
// Process chart data.
for (const key of uniqueKeys) {
- const value = key in source ? Math.round(source[key] * 100) / 100 : 0;
+ const value = source[key] || 0;
const date = new Date(key);
// @ts-expect-error
data[0].data.push({
x: date.getTime(),
- y: value && negative ? -1 * value : value,
+ y: value && type === "to_grid" ? -1 * value : value,
});
}
@@ -470,43 +459,12 @@ export class HuiEnergySummaryGraphCard
.has-header {
padding-top: 0;
}
- .chartLegend ul {
- padding-left: 20px;
- }
- .chartLegend li {
- padding: 2px 8px;
- display: flex;
- justify-content: space-between;
- align-items: center;
- text-overflow: ellipsis;
- white-space: nowrap;
- overflow: hidden;
- box-sizing: border-box;
- color: var(--secondary-text-color);
- }
- .chartLegend li > div {
- display: flex;
- align-items: center;
- }
- .chartLegend .bullet {
- border-width: 1px;
- border-style: solid;
- border-radius: 4px;
- display: inline-block;
- height: 16px;
- margin-right: 6px;
- width: 32px;
- box-sizing: border-box;
- }
- .value {
- font-weight: 300;
- }
`;
}
}
declare global {
interface HTMLElementTagNameMap {
- "hui-energy-summary-graph-card": HuiEnergySummaryGraphCard;
+ "hui-energy-usage-graph-card": HuiEnergyUsageGraphCard;
}
}
diff --git a/src/panels/lovelace/cards/types.ts b/src/panels/lovelace/cards/types.ts
index dcbf69054a..88684adfa8 100644
--- a/src/panels/lovelace/cards/types.ts
+++ b/src/panels/lovelace/cards/types.ts
@@ -101,7 +101,7 @@ export interface EnergyDistributionCardConfig extends LovelaceCardConfig {
title?: string;
prefs: EnergyPreferences;
}
-export interface EnergySummaryGraphCardConfig extends LovelaceCardConfig {
+export interface EnergyUsageGraphCardConfig extends LovelaceCardConfig {
type: "energy-summary-graph";
title?: string;
prefs: EnergyPreferences;
@@ -119,6 +119,12 @@ export interface EnergyDevicesGraphCardConfig extends LovelaceCardConfig {
prefs: EnergyPreferences;
}
+export interface EnergySourcesTableCardConfig extends LovelaceCardConfig {
+ type: "energy-sources-table";
+ title?: string;
+ prefs: EnergyPreferences;
+}
+
export interface EnergySolarGaugeCardConfig extends LovelaceCardConfig {
type: "energy-solar-consumed-gauge";
title?: string;
diff --git a/src/panels/lovelace/create-element/create-card-element.ts b/src/panels/lovelace/create-element/create-card-element.ts
index 71cee8f6fd..f6a489c098 100644
--- a/src/panels/lovelace/create-element/create-card-element.ts
+++ b/src/panels/lovelace/create-element/create-card-element.ts
@@ -35,14 +35,14 @@ const LAZY_LOAD_TYPES = {
"alarm-panel": () => import("../cards/hui-alarm-panel-card"),
error: () => import("../cards/hui-error-card"),
"empty-state": () => import("../cards/hui-empty-state-card"),
- "energy-summary-graph": () =>
- import("../cards/energy/hui-energy-summary-graph-card"),
+ "energy-usage-graph": () =>
+ import("../cards/energy/hui-energy-usage-graph-card"),
"energy-solar-graph": () =>
import("../cards/energy/hui-energy-solar-graph-card"),
"energy-devices-graph": () =>
import("../cards/energy/hui-energy-devices-graph-card"),
- "energy-costs-table": () =>
- import("../cards/energy/hui-energy-costs-table-card"),
+ "energy-sources-table": () =>
+ import("../cards/energy/hui-energy-sources-table-card"),
"energy-distribution": () =>
import("../cards/energy/hui-energy-distribution-card"),
"energy-solar-consumed-gauge": () =>
diff --git a/src/resources/ha-style.ts b/src/resources/ha-style.ts
index a4ff54ff8f..9416b15f99 100644
--- a/src/resources/ha-style.ts
+++ b/src/resources/ha-style.ts
@@ -82,6 +82,14 @@ documentContainer.innerHTML = `
--state-climate-dry-color: #efbd07;
--state-climate-idle-color: #8a8a8a;
+ /* energy */
+ --energy-grid-consumption-color: #126a9a;
+ --energy-grid-return-color: #673ab7;
+ --energy-solar-color: #ff9800;
+ --energy-non-fossil-color: #0f9d58;
+
+ --rgb-energy-solar-color: 255, 152, 0;
+
/*
Paper-styles color.html dependency is stripped on build.
When a default paper-style color is used, it needs to be copied
diff --git a/src/resources/styles.ts b/src/resources/styles.ts
index f2b2494416..716de34179 100644
--- a/src/resources/styles.ts
+++ b/src/resources/styles.ts
@@ -31,6 +31,7 @@ export const darkStyles = {
"codemirror-property": "#C792EA",
"codemirror-qualifier": "#DECB6B",
"codemirror-type": "#DECB6B",
+ "energy-grid-return-color": "#b39bdb",
};
export const derivedStyles = {
From 1bd6392a4ce2cbc6e79172f70ea991cf27d8efe8 Mon Sep 17 00:00:00 2001
From: Bram Kragten
Date: Thu, 29 Jul 2021 20:26:22 +0200
Subject: [PATCH 31/43] Add text when no statistics found (#9642)
* Add text when no statistics found
* Update src/components/entity/ha-statistic-picker.ts
Co-authored-by: Paulus Schoutsen
* fix typos
* Update src/components/entity/ha-statistic-picker.ts
* Prettier
Co-authored-by: Paulus Schoutsen
---
src/components/entity/ha-entities-picker.ts | 3 +
src/components/entity/ha-statistic-picker.ts | 115 +++++++++++-------
src/components/entity/ha-statistics-picker.ts | 3 +
src/panels/config/core/ha-config-core-form.ts | 2 +-
.../components/ha-energy-device-settings.ts | 2 +-
.../components/ha-energy-grid-settings.ts | 2 +-
.../components/ha-energy-solar-settings.ts | 2 +-
src/translations/en.json | 7 ++
8 files changed, 91 insertions(+), 45 deletions(-)
diff --git a/src/components/entity/ha-entities-picker.ts b/src/components/entity/ha-entities-picker.ts
index fff8041e72..19c20886fa 100644
--- a/src/components/entity/ha-entities-picker.ts
+++ b/src/components/entity/ha-entities-picker.ts
@@ -131,6 +131,9 @@ class HaEntitiesPickerLight extends LitElement {
private async _addEntity(event: PolymerChangedEvent) {
event.stopPropagation();
const toAdd = event.detail.value;
+ if (!toAdd) {
+ return;
+ }
(event.currentTarget as any).value = "";
if (!toAdd) {
return;
diff --git a/src/components/entity/ha-statistic-picker.ts b/src/components/entity/ha-statistic-picker.ts
index 912188ea8d..aa09f92d25 100644
--- a/src/components/entity/ha-statistic-picker.ts
+++ b/src/components/entity/ha-statistic-picker.ts
@@ -22,46 +22,12 @@ import { compare } from "../../common/string/compare";
import { getStatisticIds, StatisticsMetaData } from "../../data/history";
import { PolymerChangedEvent } from "../../polymer-types";
import { HomeAssistant } from "../../types";
+import { documentationUrl } from "../../util/documentation-url";
import "../ha-combo-box";
import type { HaComboBox } from "../ha-combo-box";
import "../ha-svg-icon";
import "./state-badge";
-// vaadin-combo-box-item
-
-const rowRenderer: ComboBoxLitRenderer<{
- id: string;
- name: string;
- state?: HassEntity;
-}> = (item) => html`
-
-
-
-
- ${item.name}
- ${item.id}
-
- `;
-
@customElement("ha-statistic-picker")
export class HaStatisticPicker extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@@ -99,6 +65,53 @@ export class HaStatisticPicker extends LitElement {
private _init = false;
+ private _rowRenderer: ComboBoxLitRenderer<{
+ id: string;
+ name: string;
+ state?: HassEntity;
+ }> = (item) => html`
+
+
+
+
+ ${item.name}
+ ${item.id === "" || item.id === "__missing"
+ ? html`${this.hass.localize(
+ "ui.components.statistic-picker.learn_more"
+ )}`
+ : item.id}
+
+ `;
+
private _getStatistics = memoizeOne(
(
statisticIds: StatisticsMetaData[],
@@ -110,7 +123,7 @@ export class HaStatisticPicker extends LitElement {
{
id: "",
name: this.hass.localize(
- "ui.components.statistics-picker.no_statistics"
+ "ui.components.statistic-picker.no_statistics"
),
},
];
@@ -142,10 +155,27 @@ export class HaStatisticPicker extends LitElement {
});
});
- if (output.length === 1) {
- return output;
+ if (!output.length) {
+ return [
+ {
+ id: "",
+ name: this.hass.localize("ui.components.statistic-picker.no_match"),
+ },
+ ];
}
- return output.sort((a, b) => compare(a.name || "", b.name || ""));
+
+ if (output.length > 1) {
+ output.sort((a, b) => compare(a.name || "", b.name || ""));
+ }
+
+ output.push({
+ id: "__missing",
+ name: this.hass.localize(
+ "ui.components.statistic-picker.missing_entity"
+ ),
+ });
+
+ return output;
}
);
@@ -195,7 +225,7 @@ export class HaStatisticPicker extends LitElement {
? this.hass.localize("ui.components.statistic-picker.statistic")
: this.label}
.value=${this._value}
- .renderer=${rowRenderer}
+ .renderer=${this._rowRenderer}
.disabled=${this.disabled}
item-value-path="id"
item-id-path="id"
@@ -216,7 +246,10 @@ export class HaStatisticPicker extends LitElement {
private _statisticChanged(ev: PolymerChangedEvent) {
ev.stopPropagation();
- const newValue = ev.detail.value;
+ let newValue = ev.detail.value;
+ if (newValue === "__missing") {
+ newValue = "";
+ }
if (newValue !== this._value) {
this._setValue(newValue);
diff --git a/src/components/entity/ha-statistics-picker.ts b/src/components/entity/ha-statistics-picker.ts
index 3ad6af9203..22a6593ffd 100644
--- a/src/components/entity/ha-statistics-picker.ts
+++ b/src/components/entity/ha-statistics-picker.ts
@@ -90,6 +90,9 @@ class HaStatisticsPicker extends LitElement {
private async _addStatistic(event: PolymerChangedEvent) {
event.stopPropagation();
const toAdd = event.detail.value;
+ if (!toAdd) {
+ return;
+ }
(event.currentTarget as any).value = "";
if (!toAdd) {
return;
diff --git a/src/panels/config/core/ha-config-core-form.ts b/src/panels/config/core/ha-config-core-form.ts
index 980f1cd5f9..edc69e73e4 100644
--- a/src/panels/config/core/ha-config-core-form.ts
+++ b/src/panels/config/core/ha-config-core-form.ts
@@ -155,7 +155,7 @@ class ConfigCoreForm extends LitElement {
${this.hass.localize(
"ui.panel.config.core.section.core.core_config.find_currency_value"
)}
Date: Thu, 29 Jul 2021 20:29:49 +0200
Subject: [PATCH 32/43] Fix energy calculations (#9647)
* Fix calculations
* max.. not min...
---
.../hui-energy-carbon-consumed-gauge-card.ts | 5 ++-
.../energy/hui-energy-distribution-card.ts | 31 ++++++-------------
2 files changed, 12 insertions(+), 24 deletions(-)
diff --git a/src/panels/lovelace/cards/energy/hui-energy-carbon-consumed-gauge-card.ts b/src/panels/lovelace/cards/energy/hui-energy-carbon-consumed-gauge-card.ts
index f682895585..84a2e54206 100644
--- a/src/panels/lovelace/cards/energy/hui-energy-carbon-consumed-gauge-card.ts
+++ b/src/panels/lovelace/cards/energy/hui-energy-carbon-consumed-gauge-card.ts
@@ -102,10 +102,9 @@ class HuiEnergyCarbonGaugeCard extends LitElement implements LovelaceCard {
const totalEnergyConsumed =
totalGridConsumption +
- (totalSolarProduction || 0) -
- (totalGridReturned || 0);
+ Math.max(0, (totalSolarProduction || 0) - (totalGridReturned || 0));
- value = round((highCarbonEnergy / totalEnergyConsumed) * 100);
+ value = round((1 - highCarbonEnergy / totalEnergyConsumed) * 100);
}
return html`
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 286940fd58..a3b27745f6 100644
--- a/src/panels/lovelace/cards/energy/hui-energy-distribution-card.ts
+++ b/src/panels/lovelace/cards/energy/hui-energy-distribution-card.ts
@@ -104,17 +104,17 @@ class HuiEnergyDistrubutionCard extends LitElement implements LovelaceCard {
) || 0;
}
- const totalConsumption =
- totalGridConsumption +
- (totalSolarProduction || 0) -
- (productionReturnedToGrid || 0);
+ const solarConsumption = Math.max(
+ 0,
+ (totalSolarProduction || 0) - (productionReturnedToGrid || 0)
+ );
+
+ const totalHomeConsumption = totalGridConsumption + solarConsumption;
let homeSolarCircumference: number | undefined;
if (hasSolarProduction) {
- const homePctSolar =
- ((totalSolarProduction || 0) - (productionReturnedToGrid || 0)) /
- totalConsumption;
- homeSolarCircumference = CIRCLE_CIRCUMFERENCE * homePctSolar;
+ homeSolarCircumference =
+ CIRCLE_CIRCUMFERENCE * (solarConsumption / totalHomeConsumption);
}
let lowCarbonConsumption: number | undefined;
@@ -142,10 +142,8 @@ class HuiEnergyDistrubutionCard extends LitElement implements LovelaceCard {
if (highCarbonConsumption !== null) {
lowCarbonConsumption = totalGridConsumption - highCarbonConsumption;
- const homePctGridHighCarbon = highCarbonConsumption / totalConsumption;
-
homeHighCarbonCircumference =
- CIRCLE_CIRCUMFERENCE * homePctGridHighCarbon;
+ CIRCLE_CIRCUMFERENCE * (highCarbonConsumption / totalHomeConsumption);
homeLowCarbonCircumference =
CIRCLE_CIRCUMFERENCE -
@@ -154,15 +152,6 @@ class HuiEnergyDistrubutionCard extends LitElement implements LovelaceCard {
}
}
- homeSolarCircumference = CIRCLE_CIRCUMFERENCE * 0.1;
-
- homeHighCarbonCircumference = CIRCLE_CIRCUMFERENCE * 0.8;
-
- homeLowCarbonCircumference =
- CIRCLE_CIRCUMFERENCE -
- (homeSolarCircumference || 0) -
- homeHighCarbonCircumference;
-
return html`
@@ -252,7 +241,7 @@ class HuiEnergyDistrubutionCard extends LitElement implements LovelaceCard {
})}"
>
- ${formatNumber(totalConsumption, this.hass.locale, {
+ ${formatNumber(totalHomeConsumption, this.hass.locale, {
maximumFractionDigits: 1,
})}
kWh
From ae10ff42e113825c434f6c0437176b21474ecbb5 Mon Sep 17 00:00:00 2001
From: Bram Kragten
Date: Thu, 29 Jul 2021 20:45:25 +0200
Subject: [PATCH 33/43] Update state-history-chart-line.ts
---
src/components/chart/state-history-chart-line.ts | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/components/chart/state-history-chart-line.ts b/src/components/chart/state-history-chart-line.ts
index 797fd55316..b2bddfba4f 100644
--- a/src/components/chart/state-history-chart-line.ts
+++ b/src/components/chart/state-history-chart-line.ts
@@ -2,6 +2,7 @@ import type { ChartData, ChartDataset, ChartOptions } from "chart.js";
import { html, LitElement, PropertyValues } from "lit";
import { property, state } from "lit/decorators";
import { getColorByIndex } from "../../common/color/colors";
+import { numberFormatToLocale } from "../../common/string/format_number";
import { LineChartEntity, LineChartState } from "../../data/history";
import { HomeAssistant } from "../../types";
import "./ha-chart-base";
From 749079c1c3d3658ca98b8f667b7c0637d35433e1 Mon Sep 17 00:00:00 2001
From: Paulus Schoutsen
Date: Thu, 29 Jul 2021 12:02:34 -0700
Subject: [PATCH 34/43] Bumped version to 20210729.0
---
setup.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/setup.py b/setup.py
index 46ad3ec8f2..e1ca544083 100644
--- a/setup.py
+++ b/setup.py
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
setup(
name="home-assistant-frontend",
- version="20210728.0",
+ version="20210729.0",
description="The Home Assistant frontend",
url="https://github.com/home-assistant/frontend",
author="The Home Assistant Authors",
From a4f51b0cb337e3c79e6a9dea8a0d8356b416642e Mon Sep 17 00:00:00 2001
From: Paulus Schoutsen
Date: Fri, 30 Jul 2021 00:42:54 -0700
Subject: [PATCH 35/43] Fix label for device consumption (#9648)
---
.../config/energy/dialogs/dialog-energy-device-settings.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/panels/config/energy/dialogs/dialog-energy-device-settings.ts b/src/panels/config/energy/dialogs/dialog-energy-device-settings.ts
index 9d3a383be5..3705b4b6a2 100644
--- a/src/panels/config/energy/dialogs/dialog-energy-device-settings.ts
+++ b/src/panels/config/energy/dialogs/dialog-energy-device-settings.ts
@@ -62,7 +62,7 @@ export class DialogEnergyDeviceSettings
From 03080973be7b1aa4e0717c1783bb9574dcc9046d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Thomas=20Lov=C3=A9n?=
Date: Fri, 30 Jul 2021 10:51:21 +0200
Subject: [PATCH 36/43] Add needle option to ha-gauge and gauge card (#9637)
---
src/components/ha-gauge.ts | 77 ++++++++++++++++---
src/panels/lovelace/cards/hui-gauge-card.ts | 16 ++++
src/panels/lovelace/cards/types.ts | 1 +
.../config-elements/hui-gauge-card-editor.ts | 30 +++++++-
src/translations/en.json | 1 +
5 files changed, 112 insertions(+), 13 deletions(-)
diff --git a/src/components/ha-gauge.ts b/src/components/ha-gauge.ts
index e5d785b435..2322f2e28a 100644
--- a/src/components/ha-gauge.ts
+++ b/src/components/ha-gauge.ts
@@ -13,6 +13,11 @@ const getAngle = (value: number, min: number, max: number) => {
return (percentage * 180) / 100;
};
+interface LevelDefinition {
+ level: number;
+ stroke: string;
+}
+
@customElement("ha-gauge")
export class Gauge extends LitElement {
@property({ type: Number }) public min = 0;
@@ -23,6 +28,10 @@ export class Gauge extends LitElement {
@property() public locale!: FrontendLocaleData;
+ @property({ type: Boolean }) public needle?: boolean;
+
+ @property() public levels?: LevelDefinition[];
+
@property() public label = "";
@state() private _angle = 0;
@@ -55,18 +64,53 @@ export class Gauge extends LitElement {
class="dial"
d="M 10 50 A 40 40 0 0 1 90 50"
>
-
+
+ ${
+ this.levels
+ ? this.levels
+ .sort((a, b) => a.level - b.level)
+ .map((level) => {
+ const angle = getAngle(level.level, this.min, this.max);
+ return svg``;
+ })
+ : ""
+ }
+ ${
+ this.needle
+ ? svg`
+ `
+ : svg``
+ }
${
// Workaround for https://github.com/home-assistant/frontend/issues/6467
isSafari
@@ -117,6 +161,15 @@ export class Gauge extends LitElement {
transform-origin: 50% 100%;
transition: all 1s ease 0s;
}
+ .needle {
+ fill: var(--primary-text-color);
+ transform-origin: 50% 100%;
+ transition: all 1s ease 0s;
+ }
+ .level {
+ fill: none;
+ stroke-width: 15;
+ }
.gauge {
display: block;
}
diff --git a/src/panels/lovelace/cards/hui-gauge-card.ts b/src/panels/lovelace/cards/hui-gauge-card.ts
index f87a9081e8..1db4cb6334 100644
--- a/src/panels/lovelace/cards/hui-gauge-card.ts
+++ b/src/panels/lovelace/cards/hui-gauge-card.ts
@@ -135,6 +135,8 @@ class HuiGaugeCard extends LitElement implements LovelaceCard {
style=${styleMap({
"--gauge-color": this._computeSeverity(entityState),
})}
+ .needle=${this._config!.needle}
+ .levels=${this._config!.needle ? this._severityLevels() : undefined}
>
${this._config.name || computeStateName(stateObj)}
@@ -200,6 +202,20 @@ class HuiGaugeCard extends LitElement implements LovelaceCard {
return severityMap.normal;
}
+ private _severityLevels() {
+ const sections = this._config!.severity;
+
+ if (!sections) {
+ return [];
+ }
+
+ const sectionsArray = Object.keys(sections);
+ return sectionsArray.map((severity) => ({
+ level: sections[severity],
+ stroke: severityMap[severity],
+ }));
+ }
+
private _handleClick(): void {
fireEvent(this, "hass-more-info", { entityId: this._config!.entity });
}
diff --git a/src/panels/lovelace/cards/types.ts b/src/panels/lovelace/cards/types.ts
index 88684adfa8..5d191334a3 100644
--- a/src/panels/lovelace/cards/types.ts
+++ b/src/panels/lovelace/cards/types.ts
@@ -164,6 +164,7 @@ export interface GaugeCardConfig extends LovelaceCardConfig {
max?: number;
severity?: SeverityConfig;
theme?: string;
+ needle?: boolean;
}
export interface ConfigEntity extends EntityConfig {
diff --git a/src/panels/lovelace/editor/config-elements/hui-gauge-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-gauge-card-editor.ts
index 16e1e049c9..3776dee7b0 100644
--- a/src/panels/lovelace/editor/config-elements/hui-gauge-card-editor.ts
+++ b/src/panels/lovelace/editor/config-elements/hui-gauge-card-editor.ts
@@ -1,7 +1,7 @@
import "@polymer/paper-input/paper-input";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
-import { assert, number, object, optional, string } from "superstruct";
+import { assert, boolean, number, object, optional, string } from "superstruct";
import { fireEvent } from "../../../../common/dom/fire_event";
import { computeRTLDirection } from "../../../../common/util/compute_rtl";
import "../../../../components/ha-formfield";
@@ -23,6 +23,7 @@ const cardConfigStruct = object({
max: optional(number()),
severity: optional(object()),
theme: optional(string()),
+ needle: optional(boolean()),
});
const includeDomains = ["counter", "input_number", "number", "sensor"];
@@ -137,6 +138,17 @@ export class HuiGaugeCardEditor
.configValue=${"max"}
@value-changed="${this._valueChanged}"
>
+
+
Date: Fri, 30 Jul 2021 12:28:47 +0200
Subject: [PATCH 37/43] fix self consumed solar (#9651)
---
.../cards/energy/hui-energy-solar-consumed-gauge-card.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/panels/lovelace/cards/energy/hui-energy-solar-consumed-gauge-card.ts b/src/panels/lovelace/cards/energy/hui-energy-solar-consumed-gauge-card.ts
index b7030d997d..3d79fe48ab 100644
--- a/src/panels/lovelace/cards/energy/hui-energy-solar-consumed-gauge-card.ts
+++ b/src/panels/lovelace/cards/energy/hui-energy-solar-consumed-gauge-card.ts
@@ -63,7 +63,7 @@ class HuiEnergySolarGaugeCard extends LitElement implements LovelaceCard {
let value: number | undefined;
if (productionReturnedToGrid !== null && totalSolarProduction) {
- const cosumedSolar = Math.min(
+ const cosumedSolar = Math.max(
0,
totalSolarProduction - productionReturnedToGrid
);
From 5234e9bce563317e9227c1f3a036f7007e9c50bb Mon Sep 17 00:00:00 2001
From: Bram Kragten
Date: Fri, 30 Jul 2021 17:36:31 +0200
Subject: [PATCH 38/43] still round numbers when not formatting them (#9653)
---
src/common/string/format_number.ts | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/src/common/string/format_number.ts b/src/common/string/format_number.ts
index a921ee50c4..01279cb715 100644
--- a/src/common/string/format_number.ts
+++ b/src/common/string/format_number.ts
@@ -1,4 +1,5 @@
import { FrontendLocaleData, NumberFormat } from "../../data/translation";
+import { round } from "../number/round";
export const numberFormatToLocale = (
localeOptions: FrontendLocaleData
@@ -60,7 +61,12 @@ export const formatNumber = (
).format(Number(num));
}
}
- return num.toString();
+ if (typeof num === "string") {
+ return num;
+ }
+ return `${round(num, options?.maximumFractionDigits).toString()}${
+ options?.style === "currency" ? ` ${options.currency}` : ""
+ }`;
};
/**
From cfad45b7c22b3bed627a9d7fa1c38e68559df8de Mon Sep 17 00:00:00 2001
From: Bram Kragten
Date: Fri, 30 Jul 2021 21:52:38 +0200
Subject: [PATCH 39/43] fix and finish statistics card (#9658)
---
src/components/chart/ha-chart-base.ts | 2 +-
src/components/chart/statistics-chart.ts | 60 ++++++--
src/components/ha-gauge.ts | 8 +-
src/panels/lovelace/cards/hui-gauge-card.ts | 2 +-
.../cards/hui-statistics-graph-card.ts | 5 +-
.../hui-statistics-graph-card-editor.ts | 134 ++++++++++++++++--
6 files changed, 179 insertions(+), 32 deletions(-)
diff --git a/src/components/chart/ha-chart-base.ts b/src/components/chart/ha-chart-base.ts
index 8c4c3cc16e..2ff0550efa 100644
--- a/src/components/chart/ha-chart-base.ts
+++ b/src/components/chart/ha-chart-base.ts
@@ -55,7 +55,7 @@ export default class HaChartBase extends LitElement {
this._setupChart();
return;
}
- if (changedProps.has("type")) {
+ if (changedProps.has("chartType")) {
this.chart.config.type = this.chartType;
}
if (changedProps.has("data")) {
diff --git a/src/components/chart/statistics-chart.ts b/src/components/chart/statistics-chart.ts
index 0ac2abb66a..68431696ba 100644
--- a/src/components/chart/statistics-chart.ts
+++ b/src/components/chart/statistics-chart.ts
@@ -38,8 +38,8 @@ class StatisticsChart extends LitElement {
@property({ type: Array }) public statTypes: Array = [
"sum",
"min",
- "max",
"mean",
+ "max",
];
@property() public chartType: ChartType = "line";
@@ -58,7 +58,7 @@ class StatisticsChart extends LitElement {
if (!this.hasUpdated) {
this._createOptions();
}
- if (changedProps.has("statisticsData")) {
+ if (changedProps.has("statisticsData") || changedProps.has("statTypes")) {
this._generateData();
}
}
@@ -164,6 +164,9 @@ class StatisticsChart extends LitElement {
}
private _generateData() {
+ if (!this.statisticsData) {
+ return;
+ }
let colorIndex = 0;
const statisticsData = Object.values(this.statisticsData);
const totalDataSets: ChartDataset<"line">[] = [];
@@ -231,21 +234,21 @@ class StatisticsChart extends LitElement {
prevValues = dataValues;
};
+ const color = getColorByIndex(colorIndex);
+ colorIndex++;
+
const addDataSet = (
nameY: string,
+ borderColor: string,
+ backgroundColor: string,
step = false,
- fill = false,
- color?: string
+ fill?: boolean | number | string
) => {
- if (!color) {
- color = getColorByIndex(colorIndex);
- colorIndex++;
- }
statDataSets.push({
label: nameY,
- fill: fill ? "origin" : false,
- borderColor: color,
- backgroundColor: color + "7F",
+ fill: fill || false,
+ borderColor,
+ backgroundColor: backgroundColor,
stepped: step ? "before" : false,
pointRadius: 0,
data: [],
@@ -254,20 +257,50 @@ class StatisticsChart extends LitElement {
const statTypes: this["statTypes"] = [];
- this.statTypes.forEach((type) => {
+ const sortedTypes = [...this.statTypes].sort((a, _b) => {
+ if (a === "min") {
+ return -1;
+ }
+ if (a === "max") {
+ return +1;
+ }
+ return 0;
+ });
+
+ const drawBands =
+ this.statTypes.includes("mean") && statisticsHaveType(stats, "mean");
+
+ sortedTypes.forEach((type) => {
if (statisticsHaveType(stats, type)) {
statTypes.push(type);
addDataSet(
`${name} (${this.hass.localize(
`ui.components.statistics_charts.statistic_types.${type}`
)})`,
- false
+ drawBands && (type === "min" || type === "max")
+ ? color + "7F"
+ : color,
+ color + "7F",
+ false,
+ drawBands
+ ? type === "min"
+ ? "+1"
+ : type === "max"
+ ? "-1"
+ : false
+ : false
);
}
});
+ let prevDate: Date | null = null;
// Process chart data.
stats.forEach((stat) => {
+ const date = new Date(stat.start);
+ if (prevDate === date) {
+ return;
+ }
+ prevDate = date;
const dataValues: Array = [];
statTypes.forEach((type) => {
let val: number | null;
@@ -278,7 +311,6 @@ class StatisticsChart extends LitElement {
}
dataValues.push(val !== null ? Math.round(val * 100) / 100 : null);
});
- const date = new Date(stat.start);
pushData(date, dataValues);
});
diff --git a/src/components/ha-gauge.ts b/src/components/ha-gauge.ts
index 2322f2e28a..81161ed64d 100644
--- a/src/components/ha-gauge.ts
+++ b/src/components/ha-gauge.ts
@@ -60,10 +60,14 @@ export class Gauge extends LitElement {
protected render() {
return svg`
`;
}
diff --git a/src/panels/energy/strategies/energy-strategy.ts b/src/panels/energy/strategies/energy-strategy.ts
index 7177e6d9a7..325bb65ada 100644
--- a/src/panels/energy/strategies/energy-strategy.ts
+++ b/src/panels/energy/strategies/energy-strategy.ts
@@ -1,4 +1,8 @@
-import { EnergyPreferences, getEnergyPreferences } from "../../../data/energy";
+import {
+ EnergyPreferences,
+ getEnergyPreferences,
+ GridSourceTypeEnergyPreference,
+} from "../../../data/energy";
import { LovelaceViewConfig } from "../../../data/lovelace";
import { LovelaceViewStrategy } from "../../lovelace/strategies/get-strategy";
@@ -39,9 +43,10 @@ export class EnergyStrategy {
view.type = "sidebar";
- const hasGrid = energyPrefs.energy_sources.some(
+ const hasGrid = energyPrefs.energy_sources.find(
(source) => source.type === "grid"
- );
+ ) as GridSourceTypeEnergyPreference;
+ const hasReturn = hasGrid && hasGrid.flow_to.length;
const hasSolar = energyPrefs.energy_sources.some(
(source) => source.type === "solar"
);
@@ -100,6 +105,15 @@ export class EnergyStrategy {
});
}
+ // Only include if we have a grid source & return.
+ if (hasReturn) {
+ view.cards!.push({
+ type: "energy-grid-neutrality-gauge",
+ prefs: energyPrefs,
+ view_layout: { position: "sidebar" },
+ });
+ }
+
// Only include if we have a grid
if (hasGrid) {
view.cards!.push({
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
new file mode 100644
index 0000000000..97f8dab168
--- /dev/null
+++ b/src/panels/lovelace/cards/energy/hui-energy-grid-neutrality-gauge-card.ts
@@ -0,0 +1,177 @@
+import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
+import { customElement, property, state } from "lit/decorators";
+import { formatNumber } from "../../../../common/string/format_number";
+import "../../../../components/ha-card";
+import "../../../../components/ha-gauge";
+import type { LevelDefinition } from "../../../../components/ha-gauge";
+import { GridSourceTypeEnergyPreference } from "../../../../data/energy";
+import {
+ calculateStatisticsSumGrowth,
+ fetchStatistics,
+ Statistics,
+} from "../../../../data/history";
+import type { HomeAssistant } from "../../../../types";
+import type { LovelaceCard } from "../../types";
+import type { EnergyGridGaugeCardConfig } from "../types";
+
+const LEVELS: LevelDefinition[] = [
+ { level: -1, stroke: "var(--label-badge-red)" },
+ { level: -0.2, stroke: "var(--label-badge-yellow)" },
+ { level: 0, stroke: "var(--label-badge-green)" },
+];
+
+@customElement("hui-energy-grid-neutrality-gauge-card")
+class HuiEnergyGridGaugeCard extends LitElement implements LovelaceCard {
+ @property({ attribute: false }) public hass?: HomeAssistant;
+
+ @state() private _config?: EnergyGridGaugeCardConfig;
+
+ @state() private _stats?: Statistics;
+
+ public getCardSize(): number {
+ return 4;
+ }
+
+ public setConfig(config: EnergyGridGaugeCardConfig): void {
+ this._config = config;
+ }
+
+ public willUpdate(changedProps) {
+ super.willUpdate(changedProps);
+
+ if (!this.hasUpdated) {
+ this._getStatistics();
+ }
+ }
+
+ protected render(): TemplateResult {
+ if (!this._config || !this.hass) {
+ return html``;
+ }
+
+ if (!this._stats) {
+ return html`Loading...`;
+ }
+
+ const prefs = this._config!.prefs;
+ const gridSource = prefs.energy_sources.find(
+ (src) => src.type === "grid"
+ ) as GridSourceTypeEnergyPreference | undefined;
+
+ let value: number | undefined;
+
+ if (!gridSource) {
+ return html``;
+ }
+
+ const consumedFromGrid = calculateStatisticsSumGrowth(
+ this._stats,
+ gridSource.flow_from.map((flow) => flow.stat_energy_from)
+ );
+
+ const returnedToGrid = calculateStatisticsSumGrowth(
+ this._stats,
+ gridSource.flow_to.map((flow) => flow.stat_energy_to)
+ );
+
+ if (consumedFromGrid !== null && returnedToGrid !== null) {
+ if (returnedToGrid > consumedFromGrid) {
+ value = 1 - consumedFromGrid / returnedToGrid;
+ } else if (returnedToGrid < consumedFromGrid) {
+ value = (1 - returnedToGrid / consumedFromGrid) * -1;
+ } else {
+ value = 0;
+ }
+ }
+
+ return html`
+
+ ${value !== undefined
+ ? html`
+
+ ${returnedToGrid! >= consumedFromGrid!
+ ? "Returned to the grid"
+ : "Consumed from the grid"}
+
`
+ : "Grid neutrality could not be calculated"}
+
+ `;
+ }
+
+ private async _getStatistics(): Promise {
+ const startDate = new Date();
+ startDate.setHours(0, 0, 0, 0);
+ startDate.setTime(startDate.getTime() - 1000 * 60 * 60); // subtract 1 hour to get a startpoint
+
+ const statistics: string[] = [];
+ const prefs = this._config!.prefs;
+ for (const source of prefs.energy_sources) {
+ if (source.type === "solar") {
+ continue;
+ }
+
+ // grid source
+ for (const flowFrom of source.flow_from) {
+ statistics.push(flowFrom.stat_energy_from);
+ }
+ for (const flowTo of source.flow_to) {
+ statistics.push(flowTo.stat_energy_to);
+ }
+ }
+
+ this._stats = await fetchStatistics(
+ this.hass!,
+ startDate,
+ undefined,
+ statistics
+ );
+ }
+
+ static get styles(): CSSResultGroup {
+ return css`
+ ha-card {
+ height: 100%;
+ overflow: hidden;
+ padding: 16px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-direction: column;
+ box-sizing: border-box;
+ }
+
+ ha-gauge {
+ width: 100%;
+ max-width: 250px;
+ }
+
+ .name {
+ text-align: center;
+ line-height: initial;
+ color: var(--primary-text-color);
+ width: 100%;
+ font-size: 15px;
+ margin-top: 8px;
+ }
+ `;
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "hui-energy-grid-neutrality-gauge-card": HuiEnergyGridGaugeCard;
+ }
+}
diff --git a/src/panels/lovelace/cards/types.ts b/src/panels/lovelace/cards/types.ts
index 5d191334a3..4894837543 100644
--- a/src/panels/lovelace/cards/types.ts
+++ b/src/panels/lovelace/cards/types.ts
@@ -131,6 +131,12 @@ export interface EnergySolarGaugeCardConfig extends LovelaceCardConfig {
prefs: EnergyPreferences;
}
+export interface EnergyGridGaugeCardConfig extends LovelaceCardConfig {
+ type: "energy-grid-result-gauge";
+ title?: string;
+ prefs: EnergyPreferences;
+}
+
export interface EnergyCarbonGaugeCardConfig extends LovelaceCardConfig {
type: "energy-carbon-consumed-gauge";
title?: string;
diff --git a/src/panels/lovelace/create-element/create-card-element.ts b/src/panels/lovelace/create-element/create-card-element.ts
index f6a489c098..e73a60619d 100644
--- a/src/panels/lovelace/create-element/create-card-element.ts
+++ b/src/panels/lovelace/create-element/create-card-element.ts
@@ -47,6 +47,8 @@ const LAZY_LOAD_TYPES = {
import("../cards/energy/hui-energy-distribution-card"),
"energy-solar-consumed-gauge": () =>
import("../cards/energy/hui-energy-solar-consumed-gauge-card"),
+ "energy-grid-neutrality-gauge": () =>
+ import("../cards/energy/hui-energy-grid-neutrality-gauge-card"),
"energy-carbon-consumed-gauge": () =>
import("../cards/energy/hui-energy-carbon-consumed-gauge-card"),
grid: () => import("../cards/hui-grid-card"),
From 5147dff67036962c3da9bea607f2427f1dc4f674 Mon Sep 17 00:00:00 2001
From: Bram Kragten
Date: Fri, 30 Jul 2021 21:57:01 +0200
Subject: [PATCH 41/43] Sidebar view: Move all cards to 1 column on mobile
(#9656)
---
.../energy/strategies/energy-strategy.ts | 34 ++++++++---------
src/panels/lovelace/views/hui-masonry-view.ts | 10 ++++-
src/panels/lovelace/views/hui-sidebar-view.ts | 37 +++++++++++++------
3 files changed, 51 insertions(+), 30 deletions(-)
diff --git a/src/panels/energy/strategies/energy-strategy.ts b/src/panels/energy/strategies/energy-strategy.ts
index 325bb65ada..86ff55d8d1 100644
--- a/src/panels/energy/strategies/energy-strategy.ts
+++ b/src/panels/energy/strategies/energy-strategy.ts
@@ -69,23 +69,6 @@ export class EnergyStrategy {
});
}
- if (hasGrid || hasSolar) {
- view.cards!.push({
- title: "Sources",
- type: "energy-sources-table",
- prefs: energyPrefs,
- });
- }
-
- // Only include if we have at least 1 device in the config.
- if (energyPrefs.device_consumption.length) {
- view.cards!.push({
- title: "Monitor individual devices",
- type: "energy-devices-graph",
- prefs: energyPrefs,
- });
- }
-
// Only include if we have a grid.
if (hasGrid) {
view.cards!.push({
@@ -96,6 +79,14 @@ export class EnergyStrategy {
});
}
+ if (hasGrid || hasSolar) {
+ view.cards!.push({
+ title: "Sources",
+ type: "energy-sources-table",
+ prefs: energyPrefs,
+ });
+ }
+
// Only include if we have a solar source.
if (hasSolar) {
view.cards!.push({
@@ -123,6 +114,15 @@ export class EnergyStrategy {
});
}
+ // Only include if we have at least 1 device in the config.
+ if (energyPrefs.device_consumption.length) {
+ view.cards!.push({
+ title: "Monitor individual devices",
+ type: "energy-devices-graph",
+ prefs: energyPrefs,
+ });
+ }
+
return view;
}
}
diff --git a/src/panels/lovelace/views/hui-masonry-view.ts b/src/panels/lovelace/views/hui-masonry-view.ts
index 5ce2edf13e..f8e7e1bbe8 100644
--- a/src/panels/lovelace/views/hui-masonry-view.ts
+++ b/src/panels/lovelace/views/hui-masonry-view.ts
@@ -64,6 +64,8 @@ export class MasonryView extends LitElement implements LovelaceViewElement {
private _mqls?: MediaQueryList[];
+ private _mqlListenerRef?: () => void;
+
public constructor() {
super();
this.addEventListener("iron-resize", (ev: Event) => ev.stopPropagation());
@@ -77,8 +79,9 @@ export class MasonryView extends LitElement implements LovelaceViewElement {
public disconnectedCallback() {
super.disconnectedCallback();
this._mqls?.forEach((mql) => {
- mql.removeListener(this._updateColumns);
+ mql.removeListener(this._mqlListenerRef!);
});
+ this._mqlListenerRef = undefined;
this._mqls = undefined;
}
@@ -112,7 +115,10 @@ export class MasonryView extends LitElement implements LovelaceViewElement {
private _initMqls() {
this._mqls = [300, 600, 900, 1200].map((width) => {
const mql = window.matchMedia(`(min-width: ${width}px)`);
- mql.addListener(this._updateColumns.bind(this));
+ if (!this._mqlListenerRef) {
+ this._mqlListenerRef = this._updateColumns.bind(this);
+ }
+ mql.addListener(this._mqlListenerRef);
return mql;
});
}
diff --git a/src/panels/lovelace/views/hui-sidebar-view.ts b/src/panels/lovelace/views/hui-sidebar-view.ts
index 79f57c2ea2..5792b52b2b 100644
--- a/src/panels/lovelace/views/hui-sidebar-view.ts
+++ b/src/panels/lovelace/views/hui-sidebar-view.ts
@@ -35,6 +35,24 @@ export class SideBarView extends LitElement implements LovelaceViewElement {
@state() private _config?: LovelaceViewConfig;
+ private _mqlListenerRef?: () => void;
+
+ private _mql?: MediaQueryList;
+
+ public connectedCallback() {
+ super.connectedCallback();
+ this._mql = window.matchMedia("(min-width: 760px)");
+ this._mqlListenerRef = this._createCards.bind(this);
+ this._mql.addListener(this._mqlListenerRef);
+ }
+
+ public disconnectedCallback() {
+ super.disconnectedCallback();
+ this._mql?.removeListener(this._mqlListenerRef!);
+ this._mqlListenerRef = undefined;
+ this._mql = undefined;
+ }
+
public setConfig(config: LovelaceViewConfig): void {
this._config = config;
}
@@ -96,8 +114,14 @@ export class SideBarView extends LitElement implements LovelaceViewElement {
private _createCards(): void {
const mainDiv = document.createElement("div");
mainDiv.id = "main";
- const sidebarDiv = document.createElement("div");
- sidebarDiv.id = "sidebar";
+
+ let sidebarDiv: HTMLDivElement;
+ if (this._mql?.matches) {
+ sidebarDiv = document.createElement("div");
+ sidebarDiv.id = "sidebar";
+ } else {
+ sidebarDiv = mainDiv;
+ }
if (this.hasUpdated) {
const oldMain = this.renderRoot.querySelector("#main");
@@ -177,15 +201,6 @@ export class SideBarView extends LitElement implements LovelaceViewElement {
margin: var(--masonry-view-card-margin, 4px 4px 8px);
}
- @media (max-width: 760px) {
- .container {
- flex-direction: column;
- }
- #sidebar {
- max-width: unset;
- }
- }
-
@media (max-width: 500px) {
.container > div > * {
margin-left: 0;
From 2982adbfa73876fb8119339d8f84f4ff1850d190 Mon Sep 17 00:00:00 2001
From: Bram Kragten
Date: Fri, 30 Jul 2021 21:57:27 +0200
Subject: [PATCH 42/43] Fix return compensation not being negative (#9654)
---
.../lovelace/cards/energy/hui-energy-sources-table-card.ts | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/src/panels/lovelace/cards/energy/hui-energy-sources-table-card.ts b/src/panels/lovelace/cards/energy/hui-energy-sources-table-card.ts
index 44f4336609..83252fe9fe 100644
--- a/src/panels/lovelace/cards/energy/hui-energy-sources-table-card.ts
+++ b/src/panels/lovelace/cards/energy/hui-energy-sources-table-card.ts
@@ -202,7 +202,7 @@ export class HuiEnergySourcesTableCard
flow.stat_cost ||
this._energyInfo!.cost_sensors[flow.stat_energy_from];
const cost = cost_stat
- ? calculateStatisticSumGrowth(this._stats![cost_stat])
+ ? calculateStatisticSumGrowth(this._stats![cost_stat]) || 0
: null;
if (cost !== null) {
totalCost += cost;
@@ -260,7 +260,8 @@ export class HuiEnergySourcesTableCard
flow.stat_compensation ||
this._energyInfo!.cost_sensors[flow.stat_energy_to];
const cost = cost_stat
- ? calculateStatisticSumGrowth(this._stats![cost_stat])
+ ? (calculateStatisticSumGrowth(this._stats![cost_stat]) ||
+ 0) * -1
: null;
if (cost !== null) {
totalCost += cost;
From 539d2b880c70b661327b88ffe0b500c1ac91ca10 Mon Sep 17 00:00:00 2001
From: Bram Kragten
Date: Fri, 30 Jul 2021 22:14:21 +0200
Subject: [PATCH 43/43] Bumped version to 20210730.0
---
setup.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/setup.py b/setup.py
index e1ca544083..758a5198f2 100644
--- a/setup.py
+++ b/setup.py
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
setup(
name="home-assistant-frontend",
- version="20210729.0",
+ version="20210730.0",
description="The Home Assistant frontend",
url="https://github.com/home-assistant/frontend",
author="The Home Assistant Authors",