1
0
mirror of https://github.com/home-assistant/frontend.git synced 2025-12-19 18:28:42 +00:00

Switch energy now chart to watts, format values to W, kW etc (#28555)

* 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
This commit is contained in:
Aidan Timson
2025-12-16 12:33:45 +00:00
committed by GitHub
parent 24a797e46a
commit 3425837de3
4 changed files with 70 additions and 20 deletions

View File

@@ -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]
);
};

View File

@@ -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<PowerSankeyCardConfig> = {
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) =>
`<div style="direction:ltr; display: inline;">
${formatNumber(value, this.hass.locale, value < 0.1 ? { maximumFractionDigits: 3 } : undefined)}
kW</div>`;
${formatPowerShort(this.hass, value)}
</div>`;
/**
* 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;
}

View File

@@ -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;
})

View File

@@ -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", () => {