From 3425837de340367a9e0b625409418cee17d92262 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Tue, 16 Dec 2025 12:33:45 +0000 Subject: [PATCH] Switch energy now chart to watts, format values to W, kW etc (#28555) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Switch energy now chart to watts * Add kW * Scale formatted value based on powers of 1000 1000 W -> 1 kW W → kW → MW → GW → TW * Explainers * Use 3 dp for kW+ and 0 for W * Add non-integer test --- src/data/energy.ts | 52 +++++++++++++++---- .../cards/energy/hui-power-sankey-card.ts | 15 +++--- .../energy/hui-power-sources-graph-card.ts | 11 ++-- test/data/energy.test.ts | 12 +++++ 4 files changed, 70 insertions(+), 20 deletions(-) diff --git a/src/data/energy.ts b/src/data/energy.ts index 035a1291f3..c24d603845 100644 --- a/src/data/energy.ts +++ b/src/data/energy.ts @@ -1363,9 +1363,9 @@ export const calculateSolarConsumedGauge = ( }; /** - * Get current power value from entity state, normalized to kW + * Get current power value from entity state, normalized to watts (W) * @param stateObj - The entity state object to get power value from - * @returns Power value in kW, or 0 if entity not found or invalid + * @returns Power value in W (watts), or undefined if entity not found or invalid */ export const getPowerFromState = (stateObj: HassEntity): number | undefined => { if (!stateObj) { @@ -1376,22 +1376,54 @@ export const getPowerFromState = (stateObj: HassEntity): number | undefined => { return undefined; } - // Normalize to kW based on unit of measurement (case-sensitive) + // Normalize to watts (W) based on unit of measurement (case-sensitive) // Supported units: GW, kW, MW, mW, TW, W const unit = stateObj.attributes.unit_of_measurement; switch (unit) { case "W": - return value / 1000; - case "mW": - return value / 1000000; - case "MW": + return value; + case "kW": return value * 1000; + case "mW": + return value / 1000; + case "MW": + return value * 1_000_000; case "GW": - return value * 1000000; + return value * 1_000_000_000; case "TW": - return value * 1000000000; + return value * 1_000_000_000_000; default: - // Assume kW if no unit or unit is kW + // Assume value is in watts (W) if no unit or an unsupported unit is provided return value; } }; + +/** + * Format power value in watts (W) to a short string with the appropriate unit + * @param hass - The HomeAssistant instance + * @param powerWatts - The power value in watts (W) + * @returns A string with the formatted power value and unit + */ +export const formatPowerShort = ( + hass: HomeAssistant, + powerWatts: number +): string => { + const units = ["W", "kW", "MW", "GW", "TW"]; + let unitIndex = 0; + let value = powerWatts; + + // Scale the unit to the appropriate power of 1000 + while (Math.abs(value) >= 1000 && unitIndex < units.length - 1) { + value /= 1000; + unitIndex++; + } + + return ( + formatNumber(value, hass.locale, { + // For watts, show no decimals. For kW and above, always show 3 decimals. + maximumFractionDigits: units[unitIndex] === "W" ? 0 : 3, + }) + + " " + + units[unitIndex] + ); +}; diff --git a/src/panels/lovelace/cards/energy/hui-power-sankey-card.ts b/src/panels/lovelace/cards/energy/hui-power-sankey-card.ts index 64ac382a92..7b6919e7e9 100644 --- a/src/panels/lovelace/cards/energy/hui-power-sankey-card.ts +++ b/src/panels/lovelace/cards/energy/hui-power-sankey-card.ts @@ -7,6 +7,7 @@ import "../../../../components/ha-card"; import "../../../../components/ha-svg-icon"; import type { EnergyData, EnergyPreferences } from "../../../../data/energy"; import { + formatPowerShort, getEnergyDataCollection, getPowerFromState, } from "../../../../data/energy"; @@ -17,7 +18,6 @@ import type { PowerSankeyCardConfig } from "../types"; import "../../../../components/chart/ha-sankey-chart"; import type { Link, Node } from "../../../../components/chart/ha-sankey-chart"; import { getGraphColorByIndex } from "../../../../common/color/colors"; -import { formatNumber } from "../../../../common/number/format_number"; import { getEntityContext } from "../../../../common/entity/context/get_entity_context"; import { MobileAwareMixin } from "../../../../mixins/mobile-aware-mixin"; @@ -26,8 +26,8 @@ const DEFAULT_CONFIG: Partial = { group_by_area: true, }; -// Minimum power threshold in kW to display a device node -const MIN_POWER_THRESHOLD = 0.01; +// Minimum power threshold in watts (W) to display a device node +const MIN_POWER_THRESHOLD = 10; interface PowerData { solar: number; @@ -472,8 +472,8 @@ class HuiPowerSankeyCard private _valueFormatter = (value: number) => `
- ${formatNumber(value, this.hass.locale, value < 0.1 ? { maximumFractionDigits: 3 } : undefined)} - kW
`; + ${formatPowerShort(this.hass, value)} + `; /** * Compute real-time power data from current entity states. @@ -719,14 +719,15 @@ class HuiPowerSankeyCard } /** - * Get current power value from entity state, normalized to kW + * Get current power value from entity state, normalized to watts (W) * @param entityId - The entity ID to get power value from - * @returns Power value in kW, or 0 if entity not found or invalid + * @returns Power value in W, or 0 if entity not found or invalid */ private _getCurrentPower(entityId: string): number { // Track this entity for state change detection this._entities.add(entityId); + // getPowerFromState returns power in W return getPowerFromState(this.hass.states[entityId]) ?? 0; } diff --git a/src/panels/lovelace/cards/energy/hui-power-sources-graph-card.ts b/src/panels/lovelace/cards/energy/hui-power-sources-graph-card.ts index fb6a4599e0..cf63066b45 100644 --- a/src/panels/lovelace/cards/energy/hui-power-sources-graph-card.ts +++ b/src/panels/lovelace/cards/energy/hui-power-sources-graph-card.ts @@ -210,9 +210,14 @@ export class HuiPowerSourcesGraphCard const { positive, negative } = this._processData( statIds[key].stats.map((id: string) => { const stats = energyData.stats[id] ?? []; - const currentState = getPowerFromState(this.hass.states[id]); - if (currentState !== undefined) { - stats.push({ start: now, end: now, mean: currentState }); + const currentStateWatts = getPowerFromState(this.hass.states[id]); + if (currentStateWatts !== undefined) { + // getPowerFromState returns power in W; convert to kW for this graph + stats.push({ + start: now, + end: now, + mean: currentStateWatts / 1000, + }); } return stats; }) diff --git a/test/data/energy.test.ts b/test/data/energy.test.ts index f7fe5605a3..7322d4e145 100644 --- a/test/data/energy.test.ts +++ b/test/data/energy.test.ts @@ -12,6 +12,7 @@ import { computeConsumptionSingle, formatConsumptionShort, calculateSolarConsumedGauge, + formatPowerShort, } from "../../src/data/energy"; import type { HomeAssistant } from "../../src/types"; @@ -171,6 +172,17 @@ describe("Energy Short Format Test", () => { "151 MWh" ); }); + it("Power Short Format", () => { + assert.strictEqual(formatPowerShort(hass, 0), "0 W"); + assert.strictEqual(formatPowerShort(hass, 10), "10 W"); + assert.strictEqual(formatPowerShort(hass, 12.2), "12 W"); + assert.strictEqual(formatPowerShort(hass, 999), "999 W"); + assert.strictEqual(formatPowerShort(hass, 1000), "1 kW"); + assert.strictEqual(formatPowerShort(hass, 1234), "1.234 kW"); + assert.strictEqual(formatPowerShort(hass, 10_500), "10.5 kW"); + assert.strictEqual(formatPowerShort(hass, 1_500_000), "1.5 MW"); + assert.strictEqual(formatPowerShort(hass, -1500), "-1.5 kW"); + }); }); describe("Energy Usage Calculation Tests", () => {