Support all Energy units in Energy integration (#148566)

This commit is contained in:
karwosts 2025-07-10 11:29:48 -07:00 committed by GitHub
parent d15baf9f9f
commit f0a636949a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 26 additions and 42 deletions

View File

@ -41,13 +41,8 @@ SUPPORTED_STATE_CLASSES = {
SensorStateClass.TOTAL, SensorStateClass.TOTAL,
SensorStateClass.TOTAL_INCREASING, SensorStateClass.TOTAL_INCREASING,
} }
VALID_ENERGY_UNITS: set[str] = { VALID_ENERGY_UNITS: set[str] = set(UnitOfEnergy)
UnitOfEnergy.GIGA_JOULE,
UnitOfEnergy.KILO_WATT_HOUR,
UnitOfEnergy.MEGA_JOULE,
UnitOfEnergy.MEGA_WATT_HOUR,
UnitOfEnergy.WATT_HOUR,
}
VALID_ENERGY_UNITS_GAS = { VALID_ENERGY_UNITS_GAS = {
UnitOfVolume.CENTUM_CUBIC_FEET, UnitOfVolume.CENTUM_CUBIC_FEET,
UnitOfVolume.CUBIC_FEET, UnitOfVolume.CUBIC_FEET,

View File

@ -21,14 +21,9 @@ from .const import DOMAIN
ENERGY_USAGE_DEVICE_CLASSES = (sensor.SensorDeviceClass.ENERGY,) ENERGY_USAGE_DEVICE_CLASSES = (sensor.SensorDeviceClass.ENERGY,)
ENERGY_USAGE_UNITS: dict[str, tuple[UnitOfEnergy, ...]] = { ENERGY_USAGE_UNITS: dict[str, tuple[UnitOfEnergy, ...]] = {
sensor.SensorDeviceClass.ENERGY: ( sensor.SensorDeviceClass.ENERGY: tuple(UnitOfEnergy)
UnitOfEnergy.GIGA_JOULE,
UnitOfEnergy.KILO_WATT_HOUR,
UnitOfEnergy.MEGA_JOULE,
UnitOfEnergy.MEGA_WATT_HOUR,
UnitOfEnergy.WATT_HOUR,
)
} }
ENERGY_PRICE_UNITS = tuple( ENERGY_PRICE_UNITS = tuple(
f"/{unit}" for units in ENERGY_USAGE_UNITS.values() for unit in units f"/{unit}" for units in ENERGY_USAGE_UNITS.values() for unit in units
) )
@ -39,13 +34,9 @@ GAS_USAGE_DEVICE_CLASSES = (
sensor.SensorDeviceClass.GAS, sensor.SensorDeviceClass.GAS,
) )
GAS_USAGE_UNITS: dict[str, tuple[UnitOfEnergy | UnitOfVolume, ...]] = { GAS_USAGE_UNITS: dict[str, tuple[UnitOfEnergy | UnitOfVolume, ...]] = {
sensor.SensorDeviceClass.ENERGY: ( sensor.SensorDeviceClass.ENERGY: ENERGY_USAGE_UNITS[
UnitOfEnergy.GIGA_JOULE, sensor.SensorDeviceClass.ENERGY
UnitOfEnergy.KILO_WATT_HOUR, ],
UnitOfEnergy.MEGA_JOULE,
UnitOfEnergy.MEGA_WATT_HOUR,
UnitOfEnergy.WATT_HOUR,
),
sensor.SensorDeviceClass.GAS: ( sensor.SensorDeviceClass.GAS: (
UnitOfVolume.CENTUM_CUBIC_FEET, UnitOfVolume.CENTUM_CUBIC_FEET,
UnitOfVolume.CUBIC_FEET, UnitOfVolume.CUBIC_FEET,

View File

@ -29,6 +29,7 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er from homeassistant.helpers import entity_registry as er
from homeassistant.setup import async_setup_component from homeassistant.setup import async_setup_component
from homeassistant.util import dt as dt_util from homeassistant.util import dt as dt_util
from homeassistant.util.unit_conversion import _WH_TO_CAL, _WH_TO_J
from homeassistant.util.unit_system import METRIC_SYSTEM, US_CUSTOMARY_SYSTEM from homeassistant.util.unit_system import METRIC_SYSTEM, US_CUSTOMARY_SYSTEM
from tests.components.recorder.common import async_wait_recording_done from tests.components.recorder.common import async_wait_recording_done
@ -748,10 +749,12 @@ async def test_cost_sensor_price_entity_total_no_reset(
@pytest.mark.parametrize( @pytest.mark.parametrize(
("energy_unit", "factor"), ("energy_unit", "factor"),
[ [
(UnitOfEnergy.MILLIWATT_HOUR, 1e6),
(UnitOfEnergy.WATT_HOUR, 1000), (UnitOfEnergy.WATT_HOUR, 1000),
(UnitOfEnergy.KILO_WATT_HOUR, 1), (UnitOfEnergy.KILO_WATT_HOUR, 1),
(UnitOfEnergy.MEGA_WATT_HOUR, 0.001), (UnitOfEnergy.MEGA_WATT_HOUR, 0.001),
(UnitOfEnergy.GIGA_JOULE, 0.001 * 3.6), (UnitOfEnergy.GIGA_JOULE, _WH_TO_J / 1e6),
(UnitOfEnergy.CALORIE, _WH_TO_CAL * 1e3),
], ],
) )
async def test_cost_sensor_handle_energy_units( async def test_cost_sensor_handle_energy_units(
@ -815,6 +818,7 @@ async def test_cost_sensor_handle_energy_units(
@pytest.mark.parametrize( @pytest.mark.parametrize(
("price_unit", "factor"), ("price_unit", "factor"),
[ [
(f"EUR/{UnitOfEnergy.MILLIWATT_HOUR}", 1e-6),
(f"EUR/{UnitOfEnergy.WATT_HOUR}", 0.001), (f"EUR/{UnitOfEnergy.WATT_HOUR}", 0.001),
(f"EUR/{UnitOfEnergy.KILO_WATT_HOUR}", 1), (f"EUR/{UnitOfEnergy.KILO_WATT_HOUR}", 1),
(f"EUR/{UnitOfEnergy.MEGA_WATT_HOUR}", 1000), (f"EUR/{UnitOfEnergy.MEGA_WATT_HOUR}", 1000),

View File

@ -12,6 +12,10 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers.json import JSON_DUMP from homeassistant.helpers.json import JSON_DUMP
from homeassistant.setup import async_setup_component from homeassistant.setup import async_setup_component
ENERGY_UNITS_STRING = ", ".join(tuple(UnitOfEnergy))
ENERGY_PRICE_UNITS_STRING = ", ".join(f"EUR/{unit}" for unit in tuple(UnitOfEnergy))
@pytest.fixture @pytest.fixture
def mock_is_entity_recorded(): def mock_is_entity_recorded():
@ -69,6 +73,7 @@ async def test_validation_empty_config(hass: HomeAssistant) -> None:
@pytest.mark.parametrize( @pytest.mark.parametrize(
("state_class", "energy_unit", "extra"), ("state_class", "energy_unit", "extra"),
[ [
("total_increasing", UnitOfEnergy.MILLIWATT_HOUR, {}),
("total_increasing", UnitOfEnergy.KILO_WATT_HOUR, {}), ("total_increasing", UnitOfEnergy.KILO_WATT_HOUR, {}),
("total_increasing", UnitOfEnergy.MEGA_WATT_HOUR, {}), ("total_increasing", UnitOfEnergy.MEGA_WATT_HOUR, {}),
("total_increasing", UnitOfEnergy.WATT_HOUR, {}), ("total_increasing", UnitOfEnergy.WATT_HOUR, {}),
@ -76,6 +81,7 @@ async def test_validation_empty_config(hass: HomeAssistant) -> None:
("total", UnitOfEnergy.KILO_WATT_HOUR, {"last_reset": "abc"}), ("total", UnitOfEnergy.KILO_WATT_HOUR, {"last_reset": "abc"}),
("measurement", UnitOfEnergy.KILO_WATT_HOUR, {"last_reset": "abc"}), ("measurement", UnitOfEnergy.KILO_WATT_HOUR, {"last_reset": "abc"}),
("total_increasing", UnitOfEnergy.GIGA_JOULE, {}), ("total_increasing", UnitOfEnergy.GIGA_JOULE, {}),
("total_increasing", UnitOfEnergy.CALORIE, {}),
], ],
) )
async def test_validation( async def test_validation(
@ -235,9 +241,7 @@ async def test_validation_device_consumption_entity_unexpected_unit(
{ {
"type": "entity_unexpected_unit_energy", "type": "entity_unexpected_unit_energy",
"affected_entities": {("sensor.unexpected_unit", "beers")}, "affected_entities": {("sensor.unexpected_unit", "beers")},
"translation_placeholders": { "translation_placeholders": {"energy_units": ENERGY_UNITS_STRING},
"energy_units": "GJ, kWh, MJ, MWh, Wh"
},
} }
] ]
], ],
@ -325,9 +329,7 @@ async def test_validation_solar(
{ {
"type": "entity_unexpected_unit_energy", "type": "entity_unexpected_unit_energy",
"affected_entities": {("sensor.solar_production", "beers")}, "affected_entities": {("sensor.solar_production", "beers")},
"translation_placeholders": { "translation_placeholders": {"energy_units": ENERGY_UNITS_STRING},
"energy_units": "GJ, kWh, MJ, MWh, Wh"
},
} }
] ]
], ],
@ -378,9 +380,7 @@ async def test_validation_battery(
("sensor.battery_import", "beers"), ("sensor.battery_import", "beers"),
("sensor.battery_export", "beers"), ("sensor.battery_export", "beers"),
}, },
"translation_placeholders": { "translation_placeholders": {"energy_units": ENERGY_UNITS_STRING},
"energy_units": "GJ, kWh, MJ, MWh, Wh"
},
}, },
] ]
], ],
@ -449,9 +449,7 @@ async def test_validation_grid(
("sensor.grid_consumption_1", "beers"), ("sensor.grid_consumption_1", "beers"),
("sensor.grid_production_1", "beers"), ("sensor.grid_production_1", "beers"),
}, },
"translation_placeholders": { "translation_placeholders": {"energy_units": ENERGY_UNITS_STRING},
"energy_units": "GJ, kWh, MJ, MWh, Wh"
},
}, },
{ {
"type": "statistics_not_defined", "type": "statistics_not_defined",
@ -538,9 +536,7 @@ async def test_validation_grid_external_cost_compensation(
("sensor.grid_consumption_1", "beers"), ("sensor.grid_consumption_1", "beers"),
("sensor.grid_production_1", "beers"), ("sensor.grid_production_1", "beers"),
}, },
"translation_placeholders": { "translation_placeholders": {"energy_units": ENERGY_UNITS_STRING},
"energy_units": "GJ, kWh, MJ, MWh, Wh"
},
}, },
{ {
"type": "statistics_not_defined", "type": "statistics_not_defined",
@ -710,9 +706,7 @@ async def test_validation_grid_auto_cost_entity_errors(
{ {
"type": "entity_unexpected_unit_energy_price", "type": "entity_unexpected_unit_energy_price",
"affected_entities": {("sensor.grid_price_1", "$/Ws")}, "affected_entities": {("sensor.grid_price_1", "$/Ws")},
"translation_placeholders": { "translation_placeholders": {"price_units": ENERGY_PRICE_UNITS_STRING},
"price_units": "EUR/GJ, EUR/kWh, EUR/MJ, EUR/MWh, EUR/Wh"
},
}, },
), ),
], ],
@ -855,7 +849,7 @@ async def test_validation_gas(
"type": "entity_unexpected_unit_gas", "type": "entity_unexpected_unit_gas",
"affected_entities": {("sensor.gas_consumption_1", "beers")}, "affected_entities": {("sensor.gas_consumption_1", "beers")},
"translation_placeholders": { "translation_placeholders": {
"energy_units": "GJ, kWh, MJ, MWh, Wh", "energy_units": ENERGY_UNITS_STRING,
"gas_units": "CCF, ft³, m³, L", "gas_units": "CCF, ft³, m³, L",
}, },
}, },
@ -885,7 +879,7 @@ async def test_validation_gas(
"affected_entities": {("sensor.gas_price_2", "EUR/invalid")}, "affected_entities": {("sensor.gas_price_2", "EUR/invalid")},
"translation_placeholders": { "translation_placeholders": {
"price_units": ( "price_units": (
"EUR/GJ, EUR/kWh, EUR/MJ, EUR/MWh, EUR/Wh, EUR/CCF, EUR/ft³, EUR/m³, EUR/L" f"{ENERGY_PRICE_UNITS_STRING}, EUR/CCF, EUR/ft³, EUR/m³, EUR/L"
) )
}, },
}, },