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:
@@ -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
|
* @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 => {
|
export const getPowerFromState = (stateObj: HassEntity): number | undefined => {
|
||||||
if (!stateObj) {
|
if (!stateObj) {
|
||||||
@@ -1376,22 +1376,54 @@ export const getPowerFromState = (stateObj: HassEntity): number | undefined => {
|
|||||||
return 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
|
// Supported units: GW, kW, MW, mW, TW, W
|
||||||
const unit = stateObj.attributes.unit_of_measurement;
|
const unit = stateObj.attributes.unit_of_measurement;
|
||||||
switch (unit) {
|
switch (unit) {
|
||||||
case "W":
|
case "W":
|
||||||
return value / 1000;
|
return value;
|
||||||
case "mW":
|
case "kW":
|
||||||
return value / 1000000;
|
|
||||||
case "MW":
|
|
||||||
return value * 1000;
|
return value * 1000;
|
||||||
|
case "mW":
|
||||||
|
return value / 1000;
|
||||||
|
case "MW":
|
||||||
|
return value * 1_000_000;
|
||||||
case "GW":
|
case "GW":
|
||||||
return value * 1000000;
|
return value * 1_000_000_000;
|
||||||
case "TW":
|
case "TW":
|
||||||
return value * 1000000000;
|
return value * 1_000_000_000_000;
|
||||||
default:
|
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;
|
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]
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import "../../../../components/ha-card";
|
|||||||
import "../../../../components/ha-svg-icon";
|
import "../../../../components/ha-svg-icon";
|
||||||
import type { EnergyData, EnergyPreferences } from "../../../../data/energy";
|
import type { EnergyData, EnergyPreferences } from "../../../../data/energy";
|
||||||
import {
|
import {
|
||||||
|
formatPowerShort,
|
||||||
getEnergyDataCollection,
|
getEnergyDataCollection,
|
||||||
getPowerFromState,
|
getPowerFromState,
|
||||||
} from "../../../../data/energy";
|
} from "../../../../data/energy";
|
||||||
@@ -17,7 +18,6 @@ import type { PowerSankeyCardConfig } from "../types";
|
|||||||
import "../../../../components/chart/ha-sankey-chart";
|
import "../../../../components/chart/ha-sankey-chart";
|
||||||
import type { Link, Node } from "../../../../components/chart/ha-sankey-chart";
|
import type { Link, Node } from "../../../../components/chart/ha-sankey-chart";
|
||||||
import { getGraphColorByIndex } from "../../../../common/color/colors";
|
import { getGraphColorByIndex } from "../../../../common/color/colors";
|
||||||
import { formatNumber } from "../../../../common/number/format_number";
|
|
||||||
import { getEntityContext } from "../../../../common/entity/context/get_entity_context";
|
import { getEntityContext } from "../../../../common/entity/context/get_entity_context";
|
||||||
import { MobileAwareMixin } from "../../../../mixins/mobile-aware-mixin";
|
import { MobileAwareMixin } from "../../../../mixins/mobile-aware-mixin";
|
||||||
|
|
||||||
@@ -26,8 +26,8 @@ const DEFAULT_CONFIG: Partial<PowerSankeyCardConfig> = {
|
|||||||
group_by_area: true,
|
group_by_area: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Minimum power threshold in kW to display a device node
|
// Minimum power threshold in watts (W) to display a device node
|
||||||
const MIN_POWER_THRESHOLD = 0.01;
|
const MIN_POWER_THRESHOLD = 10;
|
||||||
|
|
||||||
interface PowerData {
|
interface PowerData {
|
||||||
solar: number;
|
solar: number;
|
||||||
@@ -472,8 +472,8 @@ class HuiPowerSankeyCard
|
|||||||
|
|
||||||
private _valueFormatter = (value: number) =>
|
private _valueFormatter = (value: number) =>
|
||||||
`<div style="direction:ltr; display: inline;">
|
`<div style="direction:ltr; display: inline;">
|
||||||
${formatNumber(value, this.hass.locale, value < 0.1 ? { maximumFractionDigits: 3 } : undefined)}
|
${formatPowerShort(this.hass, value)}
|
||||||
kW</div>`;
|
</div>`;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compute real-time power data from current entity states.
|
* 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
|
* @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 {
|
private _getCurrentPower(entityId: string): number {
|
||||||
// Track this entity for state change detection
|
// Track this entity for state change detection
|
||||||
this._entities.add(entityId);
|
this._entities.add(entityId);
|
||||||
|
|
||||||
|
// getPowerFromState returns power in W
|
||||||
return getPowerFromState(this.hass.states[entityId]) ?? 0;
|
return getPowerFromState(this.hass.states[entityId]) ?? 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -210,9 +210,14 @@ export class HuiPowerSourcesGraphCard
|
|||||||
const { positive, negative } = this._processData(
|
const { positive, negative } = this._processData(
|
||||||
statIds[key].stats.map((id: string) => {
|
statIds[key].stats.map((id: string) => {
|
||||||
const stats = energyData.stats[id] ?? [];
|
const stats = energyData.stats[id] ?? [];
|
||||||
const currentState = getPowerFromState(this.hass.states[id]);
|
const currentStateWatts = getPowerFromState(this.hass.states[id]);
|
||||||
if (currentState !== undefined) {
|
if (currentStateWatts !== undefined) {
|
||||||
stats.push({ start: now, end: now, mean: currentState });
|
// getPowerFromState returns power in W; convert to kW for this graph
|
||||||
|
stats.push({
|
||||||
|
start: now,
|
||||||
|
end: now,
|
||||||
|
mean: currentStateWatts / 1000,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return stats;
|
return stats;
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
computeConsumptionSingle,
|
computeConsumptionSingle,
|
||||||
formatConsumptionShort,
|
formatConsumptionShort,
|
||||||
calculateSolarConsumedGauge,
|
calculateSolarConsumedGauge,
|
||||||
|
formatPowerShort,
|
||||||
} from "../../src/data/energy";
|
} from "../../src/data/energy";
|
||||||
import type { HomeAssistant } from "../../src/types";
|
import type { HomeAssistant } from "../../src/types";
|
||||||
|
|
||||||
@@ -171,6 +172,17 @@ describe("Energy Short Format Test", () => {
|
|||||||
"151 MWh"
|
"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", () => {
|
describe("Energy Usage Calculation Tests", () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user