diff --git a/README.md b/README.md index ce8963d8..9d0e2b50 100644 --- a/README.md +++ b/README.md @@ -134,11 +134,13 @@ entities: This formula is based on the offical formula used by the Energy Distribution card. ```js -max - (value / total) * (max - min); +max - (value / totalLines) * (max - min); // max = max_flow_rate // min = min_flow_rate // value = line value, solar to grid for example -// total = home consumption + solar to grid + solar to battery +// totalLines = gridConsumption + solarConsumption + solarToBattery + +// batteryConsumption + batteryFromGrid + batteryToGrid + +// (returnedToGrid ? returnedToGrid - batteryToGrid : 0) ``` I'm not 100% happy with this. I'd prefer to see the dots travel slower when flow is low, but faster when flow is high. For example if the only flow is Grid to Home, I'd like to see the dot move faster if the flow is 15kW, but slower if it's only 2kW. Right now the speed would be the same. If you have a formula you'd like to propose please submit a PR. diff --git a/src/power-flow-card.ts b/src/power-flow-card.ts index 5bf88980..32511ac7 100644 --- a/src/power-flow-card.ts +++ b/src/power-flow-card.ts @@ -32,6 +32,7 @@ export class PowerFlowCard extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; @state() private _config?: PowerFlowCardConfig; + @query("#battery-grid-flow") batteryGridFlow?: SVGSVGElement; @query("#battery-home-flow") batteryToHomeFlow?: SVGSVGElement; @query("#grid-home-flow") gridToHomeFlow?: SVGSVGElement; @query("#solar-battery-flow") solarToBatteryFlow?: SVGSVGElement; @@ -78,10 +79,12 @@ export class PowerFlowCard extends LitElement { return value * 1000; }; - private displayValue = (value: number) => - value >= this._config!.watt_threshold + private displayValue = (value: number | null) => { + if (value === null) return 0; + return value >= this._config!.watt_threshold ? `${round(value / 1000, this._config!.kw_decimals)} kW` : `${round(value, this._config!.w_decimals)} W`; + }; protected render(): TemplateResult { if (!this._config || !this.hass) { @@ -95,81 +98,144 @@ export class PowerFlowCard extends LitElement { const hasReturnToGrid = typeof entities.grid === "string" || entities.grid.production; - const batteryChargeState = entities.battery_charge?.length - ? this.getEntityState(entities.battery_charge) - : null; - - const solarState = this.entityInverted("solar") - ? Math.abs(Math.min(this.getEntityStateWatts(entities.solar), 0)) - : Math.max(this.getEntityStateWatts(entities.solar), 0); - - let solarToGrid = 0; - if (hasReturnToGrid) { - if (typeof entities.grid === "string") { - if (this.entityInverted("grid")) - solarToGrid = Math.max(this.getEntityStateWatts(entities.grid), 0); - else - solarToGrid = Math.abs( - Math.min(this.getEntityStateWatts(entities.grid), 0) - ); - } else solarToGrid = this.getEntityStateWatts(entities.grid.production); - } - - let batteryToHome = 0; - if (typeof entities.battery === "string") { - if (this.entityInverted("battery")) - batteryToHome = Math.abs( - Math.min(this.getEntityStateWatts(entities.battery), 0) - ); - else - batteryToHome = Math.max(this.getEntityStateWatts(entities.battery), 0); - } else { - batteryToHome = this.getEntityStateWatts(entities.battery?.consumption); - } - - let gridToHome = 0; + let totalFromGrid = 0; if (typeof entities.grid === "string") { if (this.entityInverted("grid")) - gridToHome = Math.abs( + totalFromGrid = Math.abs( Math.min(this.getEntityStateWatts(entities.grid), 0) ); - else gridToHome = Math.max(this.getEntityStateWatts(entities.grid), 0); + else totalFromGrid = Math.max(this.getEntityStateWatts(entities.grid), 0); } else { - gridToHome = this.getEntityStateWatts(entities.grid.consumption); + totalFromGrid = this.getEntityStateWatts(entities.grid.consumption); } - let solarToBattery = 0; - if (typeof entities.battery === "string") { - if (this.entityInverted("battery")) - solarToBattery = Math.max( - this.getEntityStateWatts(entities.battery), - 0 + let totalSolarProduction: number = 0; + if (hasSolarProduction) { + if (this.entityInverted("solar")) + totalSolarProduction = Math.abs( + Math.min(this.getEntityStateWatts(entities.solar), 0) ); else - solarToBattery = Math.abs( - Math.min(this.getEntityStateWatts(entities.battery), 0) + totalSolarProduction = Math.max( + this.getEntityStateWatts(entities.solar), + 0 ); - } else { - solarToBattery = this.getEntityStateWatts(entities.battery?.production); } - const solarToHome = solarState - solarToGrid - solarToBattery; + let totalBatteryIn: number | null = null; + let totalBatteryOut: number | null = null; + if (hasBattery) { + if (typeof entities.battery === "string") { + totalBatteryIn = this.entityInverted("battery") + ? Math.max(this.getEntityStateWatts(entities.battery), 0) + : Math.abs(Math.min(this.getEntityStateWatts(entities.battery), 0)); + totalBatteryOut = this.entityInverted("battery") + ? Math.abs(Math.min(this.getEntityStateWatts(entities.battery), 0)) + : Math.max(this.getEntityStateWatts(entities.battery), 0); + } else { + totalBatteryIn = this.getEntityStateWatts(entities.battery?.production); + totalBatteryOut = this.getEntityStateWatts( + entities.battery?.consumption + ); + } + } + + let returnedToGrid: number | null = null; + if (hasReturnToGrid) { + if (typeof entities.grid === "string") { + returnedToGrid = this.entityInverted("grid") + ? Math.max(this.getEntityStateWatts(entities.grid), 0) + : Math.abs(Math.min(this.getEntityStateWatts(entities.grid), 0)); + } else { + returnedToGrid = this.getEntityStateWatts(entities.grid.production); + } + } + + let solarConsumption: number | null = null; + if (hasSolarProduction) { + solarConsumption = + totalSolarProduction - (returnedToGrid ?? 0) - (totalBatteryIn ?? 0); + } + + let batteryFromGrid: null | number = null; + let batteryToGrid: null | number = null; + if (solarConsumption !== null && solarConsumption < 0) { + // What we returned to the grid and what went in to the battery is more + // than produced, so we have used grid energy to fill the battery or + // returned battery energy to the grid + if (hasBattery) { + batteryFromGrid = Math.abs(solarConsumption); + if (batteryFromGrid > totalFromGrid) { + batteryToGrid = Math.min(batteryFromGrid - totalFromGrid, 0); + batteryFromGrid = totalFromGrid; + } + } + solarConsumption = 0; + } + + let solarToBattery: null | number = null; + if (hasSolarProduction && hasBattery) { + if (!batteryToGrid) { + batteryToGrid = Math.max( + 0, + (returnedToGrid || 0) - + (totalSolarProduction || 0) - + (totalBatteryIn || 0) - + (batteryFromGrid || 0) + ); + } + solarToBattery = totalBatteryIn! - (batteryFromGrid || 0); + } else if (!hasSolarProduction && hasBattery) { + batteryToGrid = returnedToGrid; + } + + //! Clean up this name + let solarToGrid = 0; + if (hasSolarProduction && returnedToGrid) + solarToGrid = returnedToGrid - (batteryToGrid ?? 0); + + let batteryConsumption: number | null = null; + if (hasBattery) { + batteryConsumption = (totalBatteryOut ?? 0) - (batteryToGrid ?? 0); + } + + const gridConsumption = Math.max(totalFromGrid - (batteryFromGrid ?? 0), 0); - const homeConsumption = batteryToHome + gridToHome + solarToHome; - const totalConsumption = homeConsumption + solarToBattery + solarToGrid; + const totalHomeConsumption = Math.max( + gridConsumption + (solarConsumption ?? 0) + (batteryConsumption ?? 0), + 0 + ); let homeBatteryCircumference: number | undefined; - if (hasBattery) + if (batteryConsumption) homeBatteryCircumference = - CIRCLE_CIRCUMFERENCE * (batteryToHome / homeConsumption); + CIRCLE_CIRCUMFERENCE * (batteryConsumption / totalHomeConsumption); let homeSolarCircumference: number | undefined; - if (hasSolarProduction) + if (hasSolarProduction) { homeSolarCircumference = - CIRCLE_CIRCUMFERENCE * (solarToHome / homeConsumption); + CIRCLE_CIRCUMFERENCE * (solarConsumption! / totalHomeConsumption); + } - const homeHighCarbonCircumference = - CIRCLE_CIRCUMFERENCE * (gridToHome / homeConsumption); + const homeGridCircumference = + CIRCLE_CIRCUMFERENCE * + ((totalHomeConsumption - + (batteryConsumption ?? 0) - + (solarConsumption ?? 0)) / + totalHomeConsumption); + + const totalLines = + gridConsumption + + (solarConsumption ?? 0) + + solarToGrid + + (solarToBattery ?? 0) + + (batteryConsumption ?? 0) + + (batteryFromGrid ?? 0) + + (batteryToGrid ?? 0); + + const batteryChargeState = entities.battery_charge?.length + ? this.getEntityState(entities.battery_charge) + : null; let batteryIcon = mdiBatteryHigh; if (batteryChargeState === null) { @@ -183,22 +249,27 @@ export class PowerFlowCard extends LitElement { } const newDur = { - batteryToHome: this.circleRate(batteryToHome, totalConsumption), - gridToHome: this.circleRate(gridToHome, totalConsumption), - solarToBattery: this.circleRate(solarToBattery, totalConsumption), - solarToGrid: this.circleRate(solarToGrid, totalConsumption), - solarToHome: this.circleRate(solarToHome, totalConsumption), + batteryGrid: this.circleRate( + batteryFromGrid ?? batteryToGrid ?? 0, + totalLines + ), + batteryToHome: this.circleRate(batteryConsumption ?? 0, totalLines), + gridToHome: this.circleRate(gridConsumption, totalLines), + solarToBattery: this.circleRate(solarToBattery ?? 0, totalLines), + solarToGrid: this.circleRate(solarToGrid, totalLines), + solarToHome: this.circleRate(solarConsumption ?? 0, totalLines), }; // Smooth duration changes [ + "batteryGrid", "batteryToHome", "gridToHome", "solarToBattery", "solarToGrid", "solarToHome", ].forEach((flowName) => { - const flowSVGElement = this[`${flowName}Flow`]; + const flowSVGElement = this[`${flowName}Flow`] as SVGSVGElement; if ( flowSVGElement && this.previousDur[flowName] && @@ -216,29 +287,35 @@ export class PowerFlowCard extends LitElement {
${hasSolarProduction - ? html`
- ${this.hass.localize( - "ui.panel.lovelace.cards.energy.energy_distribution.solar" - )} -
- - ${this.displayValue(solarState)} + ? html`
+
+
+ ${this.hass.localize( + "ui.panel.lovelace.cards.energy.energy_distribution.solar" + )} +
+ + + ${this.displayValue(totalSolarProduction)} +
+
` : html``}
- ${hasReturnToGrid + ${returnedToGrid !== null ? html` ${this.displayValue(solarToGrid)} + >${this.displayValue(returnedToGrid)} ` : null} @@ -246,7 +323,7 @@ export class PowerFlowCard extends LitElement { class="small" .path=${mdiArrowRight} >${this.displayValue(gridToHome)} + >${this.displayValue(totalFromGrid)}
- ${this.displayValue(homeConsumption)} + ${this.displayValue(totalHomeConsumption)} ${homeSolarCircumference !== undefined ? html` ${homeSolarCircumference !== undefined @@ -302,12 +379,12 @@ export class PowerFlowCard extends LitElement { cx="40" cy="40" r="38" - stroke-dasharray="${homeHighCarbonCircumference ?? + stroke-dasharray="${homeGridCircumference ?? CIRCLE_CIRCUMFERENCE - homeSolarCircumference! - (homeBatteryCircumference || - 0)} ${homeHighCarbonCircumference !== undefined - ? CIRCLE_CIRCUMFERENCE - homeHighCarbonCircumference + 0)} ${homeGridCircumference !== undefined + ? CIRCLE_CIRCUMFERENCE - homeGridCircumference : homeSolarCircumference! + (homeBatteryCircumference || 0)}" stroke-dashoffset="0" @@ -342,14 +419,14 @@ export class PowerFlowCard extends LitElement { class="small" .path=${mdiArrowDown} >${this.displayValue(solarToBattery)}${this.displayValue(totalBatteryIn)} ${this.displayValue(batteryToHome)}${this.displayValue(totalBatteryOut)}
` : ""} + ${hasSolarProduction + ? html`
+ + + ${solarConsumption + ? svg` + + + + ` + : ""} + +
` + : ""} + ${hasReturnToGrid && hasSolarProduction + ? html`
+ + + ${solarToGrid && hasSolarProduction + ? svg` + + + + ` + : ""} + +
` + : ""} + ${hasBattery && hasSolarProduction + ? html`
+ + + ${solarToBattery + ? svg` + + + + ` + : ""} + +
` + : ""}
- ${gridToHome + ${gridConsumption ? svg`
-
- - ${hasSolarProduction - ? svg`` - : ""} - ${solarToHome > 0 - ? svg` - - - - ` - : ""} - -
-
- - ${hasReturnToGrid && hasSolarProduction - ? svg` ` - : ""} - ${solarToGrid > 0 && hasSolarProduction - ? svg` - - - - ` - : ""} - -
-
- - ${hasBattery && hasSolarProduction - ? svg`` - : ""} - ${solarToBattery - ? svg` - - - - ` - : ""} - -
-
- - ${hasBattery - ? svg` + + - ` - : ""} - ${batteryToHome > 0 - ? svg` + + + + ` + : ""} + +
` + : ""} + ${hasBattery + ? html`
+ + + ${batteryFromGrid + ? svg` - + ` - : ""} - -
+ : ""} + ${batteryToGrid + ? svg` + + + + ` + : ""} + +
` + : ""}
${this._config.dashboard_link ? html` @@ -676,6 +803,12 @@ export class PowerFlowCard extends LitElement { .battery-out { color: var(--energy-battery-out-color); } + path.battery-from-grid { + stroke: var(--energy-grid-consumption-color); + } + path.battery-to-grid { + stroke: var(--energy-grid-return-color); + } path.return, circle.return, circle.battery-to-grid {