Add reactive energy device class and units (#143941)

This commit is contained in:
alorente 2025-05-15 13:05:46 +02:00 committed by GitHub
parent 66ecc4d69d
commit 1d47dc41c9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 96 additions and 2 deletions

View File

@ -33,6 +33,7 @@ from homeassistant.const import (
UnitOfPower,
UnitOfPrecipitationDepth,
UnitOfPressure,
UnitOfReactiveEnergy,
UnitOfReactivePower,
UnitOfSoundPressure,
UnitOfSpeed,
@ -44,6 +45,7 @@ from homeassistant.const import (
)
from homeassistant.util.unit_conversion import (
BaseUnitConverter,
ReactiveEnergyConverter,
TemperatureConverter,
VolumeFlowRateConverter,
)
@ -320,6 +322,12 @@ class NumberDeviceClass(StrEnum):
- `psi`
"""
REACTIVE_ENERGY = "reactive_energy"
"""Reactive energy.
Unit of measurement: `varh`, `kvarh`
"""
REACTIVE_POWER = "reactive_power"
"""Reactive power.
@ -498,6 +506,7 @@ DEVICE_CLASS_UNITS: dict[NumberDeviceClass, set[type[StrEnum] | str | None]] = {
NumberDeviceClass.PRECIPITATION: set(UnitOfPrecipitationDepth),
NumberDeviceClass.PRECIPITATION_INTENSITY: set(UnitOfVolumetricFlux),
NumberDeviceClass.PRESSURE: set(UnitOfPressure),
NumberDeviceClass.REACTIVE_ENERGY: set(UnitOfReactiveEnergy),
NumberDeviceClass.REACTIVE_POWER: set(UnitOfReactivePower),
NumberDeviceClass.SIGNAL_STRENGTH: {
SIGNAL_STRENGTH_DECIBELS,
@ -531,6 +540,7 @@ DEVICE_CLASS_UNITS: dict[NumberDeviceClass, set[type[StrEnum] | str | None]] = {
}
UNIT_CONVERTERS: dict[NumberDeviceClass, type[BaseUnitConverter]] = {
NumberDeviceClass.REACTIVE_ENERGY: ReactiveEnergyConverter,
NumberDeviceClass.TEMPERATURE: TemperatureConverter,
NumberDeviceClass.VOLUME_FLOW_RATE: VolumeFlowRateConverter,
}

View File

@ -111,6 +111,9 @@
"pressure": {
"default": "mdi:gauge"
},
"reactive_energy": {
"default": "mdi:lightning-bolt"
},
"reactive_power": {
"default": "mdi:flash"
},

View File

@ -130,6 +130,9 @@
"pressure": {
"name": "[%key:component::sensor::entity_component::pressure::name%]"
},
"reactive_energy": {
"name": "[%key:component::sensor::entity_component::reactive_energy::name%]"
},
"reactive_power": {
"name": "[%key:component::sensor::entity_component::reactive_power::name%]"
},

View File

@ -120,6 +120,7 @@
"precipitation": "[%key:component::sensor::entity_component::precipitation::name%]",
"precipitation_intensity": "[%key:component::sensor::entity_component::precipitation_intensity::name%]",
"pressure": "[%key:component::sensor::entity_component::pressure::name%]",
"reactive_energy": "[%key:component::sensor::entity_component::reactive_energy::name%]",
"reactive_power": "[%key:component::sensor::entity_component::reactive_power::name%]",
"signal_strength": "[%key:component::sensor::entity_component::signal_strength::name%]",
"sound_pressure": "[%key:component::sensor::entity_component::sound_pressure::name%]",

View File

@ -57,6 +57,7 @@ from homeassistant.util.unit_conversion import (
MassConverter,
PowerConverter,
PressureConverter,
ReactiveEnergyConverter,
SpeedConverter,
TemperatureConverter,
UnitlessRatioConverter,
@ -208,6 +209,7 @@ STATISTIC_UNIT_TO_UNIT_CONVERTER: dict[str | None, type[BaseUnitConverter]] = {
**dict.fromkeys(MassConverter.VALID_UNITS, MassConverter),
**dict.fromkeys(PowerConverter.VALID_UNITS, PowerConverter),
**dict.fromkeys(PressureConverter.VALID_UNITS, PressureConverter),
**dict.fromkeys(ReactiveEnergyConverter.VALID_UNITS, ReactiveEnergyConverter),
**dict.fromkeys(SpeedConverter.VALID_UNITS, SpeedConverter),
**dict.fromkeys(TemperatureConverter.VALID_UNITS, TemperatureConverter),
**dict.fromkeys(UnitlessRatioConverter.VALID_UNITS, UnitlessRatioConverter),

View File

@ -33,6 +33,7 @@ from homeassistant.const import (
UnitOfPower,
UnitOfPrecipitationDepth,
UnitOfPressure,
UnitOfReactiveEnergy,
UnitOfReactivePower,
UnitOfSoundPressure,
UnitOfSpeed,
@ -58,6 +59,7 @@ from homeassistant.util.unit_conversion import (
MassConverter,
PowerConverter,
PressureConverter,
ReactiveEnergyConverter,
SpeedConverter,
TemperatureConverter,
UnitlessRatioConverter,
@ -349,6 +351,12 @@ class SensorDeviceClass(StrEnum):
- `psi`
"""
REACTIVE_ENERGY = "reactive_energy"
"""Reactive energy.
Unit of measurement: `varh`, `kvarh`
"""
REACTIVE_POWER = "reactive_power"
"""Reactive power.
@ -529,6 +537,7 @@ UNIT_CONVERTERS: dict[SensorDeviceClass | str | None, type[BaseUnitConverter]] =
SensorDeviceClass.PRECIPITATION: DistanceConverter,
SensorDeviceClass.PRECIPITATION_INTENSITY: SpeedConverter,
SensorDeviceClass.PRESSURE: PressureConverter,
SensorDeviceClass.REACTIVE_ENERGY: ReactiveEnergyConverter,
SensorDeviceClass.SPEED: SpeedConverter,
SensorDeviceClass.TEMPERATURE: TemperatureConverter,
SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS_PARTS: UnitlessRatioConverter,
@ -597,6 +606,7 @@ DEVICE_CLASS_UNITS: dict[SensorDeviceClass, set[type[StrEnum] | str | None]] = {
SensorDeviceClass.PRECIPITATION: set(UnitOfPrecipitationDepth),
SensorDeviceClass.PRECIPITATION_INTENSITY: set(UnitOfVolumetricFlux),
SensorDeviceClass.PRESSURE: set(UnitOfPressure),
SensorDeviceClass.REACTIVE_ENERGY: set(UnitOfReactiveEnergy),
SensorDeviceClass.REACTIVE_POWER: set(UnitOfReactivePower),
SensorDeviceClass.SIGNAL_STRENGTH: {
SIGNAL_STRENGTH_DECIBELS,
@ -672,6 +682,10 @@ DEVICE_CLASS_STATE_CLASSES: dict[SensorDeviceClass, set[SensorStateClass]] = {
SensorDeviceClass.PRECIPITATION: set(SensorStateClass),
SensorDeviceClass.PRECIPITATION_INTENSITY: {SensorStateClass.MEASUREMENT},
SensorDeviceClass.PRESSURE: {SensorStateClass.MEASUREMENT},
SensorDeviceClass.REACTIVE_ENERGY: {
SensorStateClass.TOTAL,
SensorStateClass.TOTAL_INCREASING,
},
SensorDeviceClass.REACTIVE_POWER: {SensorStateClass.MEASUREMENT},
SensorDeviceClass.SIGNAL_STRENGTH: {SensorStateClass.MEASUREMENT},
SensorDeviceClass.SOUND_PRESSURE: {SensorStateClass.MEASUREMENT},

View File

@ -70,6 +70,7 @@ CONF_IS_PRECIPITATION = "is_precipitation"
CONF_IS_PRECIPITATION_INTENSITY = "is_precipitation_intensity"
CONF_IS_PRESSURE = "is_pressure"
CONF_IS_SPEED = "is_speed"
CONF_IS_REACTIVE_ENERGY = "is_reactive_energy"
CONF_IS_REACTIVE_POWER = "is_reactive_power"
CONF_IS_SIGNAL_STRENGTH = "is_signal_strength"
CONF_IS_SOUND_PRESSURE = "is_sound_pressure"
@ -128,6 +129,7 @@ ENTITY_CONDITIONS = {
{CONF_TYPE: CONF_IS_PRECIPITATION_INTENSITY}
],
SensorDeviceClass.PRESSURE: [{CONF_TYPE: CONF_IS_PRESSURE}],
SensorDeviceClass.REACTIVE_ENERGY: [{CONF_TYPE: CONF_IS_REACTIVE_ENERGY}],
SensorDeviceClass.REACTIVE_POWER: [{CONF_TYPE: CONF_IS_REACTIVE_POWER}],
SensorDeviceClass.SIGNAL_STRENGTH: [{CONF_TYPE: CONF_IS_SIGNAL_STRENGTH}],
SensorDeviceClass.SOUND_PRESSURE: [{CONF_TYPE: CONF_IS_SOUND_PRESSURE}],
@ -193,6 +195,7 @@ CONDITION_SCHEMA = vol.All(
CONF_IS_PRECIPITATION,
CONF_IS_PRECIPITATION_INTENSITY,
CONF_IS_PRESSURE,
CONF_IS_REACTIVE_ENERGY,
CONF_IS_REACTIVE_POWER,
CONF_IS_SIGNAL_STRENGTH,
CONF_IS_SOUND_PRESSURE,

View File

@ -68,6 +68,7 @@ CONF_POWER_FACTOR = "power_factor"
CONF_PRECIPITATION = "precipitation"
CONF_PRECIPITATION_INTENSITY = "precipitation_intensity"
CONF_PRESSURE = "pressure"
CONF_REACTIVE_ENERGY = "reactive_energy"
CONF_REACTIVE_POWER = "reactive_power"
CONF_SIGNAL_STRENGTH = "signal_strength"
CONF_SOUND_PRESSURE = "sound_pressure"
@ -127,6 +128,7 @@ ENTITY_TRIGGERS = {
{CONF_TYPE: CONF_PRECIPITATION_INTENSITY}
],
SensorDeviceClass.PRESSURE: [{CONF_TYPE: CONF_PRESSURE}],
SensorDeviceClass.REACTIVE_ENERGY: [{CONF_TYPE: CONF_REACTIVE_ENERGY}],
SensorDeviceClass.REACTIVE_POWER: [{CONF_TYPE: CONF_REACTIVE_POWER}],
SensorDeviceClass.SIGNAL_STRENGTH: [{CONF_TYPE: CONF_SIGNAL_STRENGTH}],
SensorDeviceClass.SOUND_PRESSURE: [{CONF_TYPE: CONF_SOUND_PRESSURE}],
@ -193,6 +195,7 @@ TRIGGER_SCHEMA = vol.All(
CONF_PRECIPITATION,
CONF_PRECIPITATION_INTENSITY,
CONF_PRESSURE,
CONF_REACTIVE_ENERGY,
CONF_REACTIVE_POWER,
CONF_SIGNAL_STRENGTH,
CONF_SOUND_PRESSURE,

View File

@ -114,6 +114,9 @@
"pressure": {
"default": "mdi:gauge"
},
"reactive_energy": {
"default": "mdi:lightning-bolt"
},
"reactive_power": {
"default": "mdi:flash"
},

View File

@ -38,6 +38,7 @@
"is_precipitation": "Current {entity_name} precipitation",
"is_precipitation_intensity": "Current {entity_name} precipitation intensity",
"is_pressure": "Current {entity_name} pressure",
"is_reactive_energy": "Current {entity_name} reactive energy",
"is_reactive_power": "Current {entity_name} reactive power",
"is_signal_strength": "Current {entity_name} signal strength",
"is_sound_pressure": "Current {entity_name} sound pressure",
@ -92,6 +93,7 @@
"precipitation": "{entity_name} precipitation changes",
"precipitation_intensity": "{entity_name} precipitation intensity changes",
"pressure": "{entity_name} pressure changes",
"reactive_energy": "{entity_name} reactive energy changes",
"reactive_power": "{entity_name} reactive power changes",
"signal_strength": "{entity_name} signal strength changes",
"sound_pressure": "{entity_name} sound pressure changes",
@ -256,6 +258,9 @@
"pressure": {
"name": "Pressure"
},
"reactive_energy": {
"name": "Reactive energy"
},
"reactive_power": {
"name": "Reactive power"
},

View File

@ -106,6 +106,7 @@
"precipitation": "[%key:component::sensor::entity_component::precipitation::name%]",
"precipitation_intensity": "[%key:component::sensor::entity_component::precipitation_intensity::name%]",
"pressure": "[%key:component::sensor::entity_component::pressure::name%]",
"reactive_energy": "[%key:component::sensor::entity_component::reactive_energy::name%]",
"reactive_power": "[%key:component::sensor::entity_component::reactive_power::name%]",
"signal_strength": "[%key:component::sensor::entity_component::signal_strength::name%]",
"sound_pressure": "[%key:component::sensor::entity_component::sound_pressure::name%]",

View File

@ -326,6 +326,7 @@
"precipitation": "[%key:component::sensor::entity_component::precipitation::name%]",
"precipitation_intensity": "[%key:component::sensor::entity_component::precipitation_intensity::name%]",
"pressure": "[%key:component::sensor::entity_component::pressure::name%]",
"reactive_energy": "[%key:component::sensor::entity_component::reactive_energy::name%]",
"reactive_power": "[%key:component::sensor::entity_component::reactive_power::name%]",
"signal_strength": "[%key:component::sensor::entity_component::signal_strength::name%]",
"sound_pressure": "[%key:component::sensor::entity_component::sound_pressure::name%]",

View File

@ -634,6 +634,14 @@ class UnitOfEnergy(StrEnum):
GIGA_CALORIE = "Gcal"
# Reactive energy units
class UnitOfReactiveEnergy(StrEnum):
"""Reactive energy units."""
VOLT_AMPERE_REACTIVE_HOUR = "varh"
KILO_VOLT_AMPERE_REACTIVE_HOUR = "kvarh"
# Energy Distance units
class UnitOfEnergyDistance(StrEnum):
"""Energy Distance units."""

View File

@ -24,6 +24,7 @@ from homeassistant.const import (
UnitOfMass,
UnitOfPower,
UnitOfPressure,
UnitOfReactiveEnergy,
UnitOfSpeed,
UnitOfTemperature,
UnitOfTime,
@ -429,6 +430,17 @@ class PressureConverter(BaseUnitConverter):
}
class ReactiveEnergyConverter(BaseUnitConverter):
"""Utility to convert reactive energy values."""
UNIT_CLASS = "energy"
_UNIT_CONVERSION: dict[str | None, float] = {
UnitOfReactiveEnergy.VOLT_AMPERE_REACTIVE_HOUR: 1,
UnitOfReactiveEnergy.KILO_VOLT_AMPERE_REACTIVE_HOUR: 1 / 1e3,
}
VALID_UNITS = set(UnitOfReactiveEnergy)
class SpeedConverter(BaseUnitConverter):
"""Utility to convert speed values."""

View File

@ -14,6 +14,7 @@ from homeassistant.const import (
UnitOfApparentPower,
UnitOfFrequency,
UnitOfPressure,
UnitOfReactiveEnergy,
UnitOfReactivePower,
UnitOfVolume,
)
@ -44,6 +45,7 @@ UNITS_OF_MEASUREMENT = {
SensorDeviceClass.ENERGY: "kWh", # energy (Wh/kWh/MWh)
SensorDeviceClass.FREQUENCY: UnitOfFrequency.GIGAHERTZ, # energy (Hz/kHz/MHz/GHz)
SensorDeviceClass.POWER_FACTOR: PERCENTAGE, # power factor (no unit, min: -1.0, max: 1.0)
SensorDeviceClass.REACTIVE_ENERGY: UnitOfReactiveEnergy.VOLT_AMPERE_REACTIVE_HOUR, # reactive energy (varh)
SensorDeviceClass.REACTIVE_POWER: UnitOfReactivePower.VOLT_AMPERE_REACTIVE, # reactive power (var)
SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS: CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, # µg/m³ of vocs
SensorDeviceClass.VOLTAGE: "V", # voltage (V)

View File

@ -119,7 +119,7 @@ async def test_get_conditions(
conditions = await async_get_device_automations(
hass, DeviceAutomationType.CONDITION, device_entry.id
)
assert len(conditions) == 27
assert len(conditions) == 28
assert conditions == unordered(expected_conditions)

View File

@ -121,7 +121,7 @@ async def test_get_triggers(
triggers = await async_get_device_automations(
hass, DeviceAutomationType.TRIGGER, device_entry.id
)
assert len(triggers) == 27
assert len(triggers) == 28
assert triggers == unordered(expected_triggers)

View File

@ -1994,6 +1994,7 @@ async def test_non_numeric_device_class_with_unit_of_measurement(
SensorDeviceClass.PRECIPITATION_INTENSITY,
SensorDeviceClass.PRECIPITATION,
SensorDeviceClass.PRESSURE,
SensorDeviceClass.REACTIVE_ENERGY,
SensorDeviceClass.REACTIVE_POWER,
SensorDeviceClass.SIGNAL_STRENGTH,
SensorDeviceClass.SOUND_PRESSURE,

View File

@ -24,6 +24,7 @@ from homeassistant.const import (
UnitOfMass,
UnitOfPower,
UnitOfPressure,
UnitOfReactiveEnergy,
UnitOfSpeed,
UnitOfTemperature,
UnitOfTime,
@ -49,6 +50,7 @@ from homeassistant.util.unit_conversion import (
MassConverter,
PowerConverter,
PressureConverter,
ReactiveEnergyConverter,
SpeedConverter,
TemperatureConverter,
UnitlessRatioConverter,
@ -78,6 +80,7 @@ _ALL_CONVERTERS: dict[type[BaseUnitConverter], list[str | None]] = {
MassConverter,
PowerConverter,
PressureConverter,
ReactiveEnergyConverter,
SpeedConverter,
TemperatureConverter,
UnitlessRatioConverter,
@ -127,6 +130,11 @@ _GET_UNIT_RATIO: dict[type[BaseUnitConverter], tuple[str | None, str | None, flo
MassConverter: (UnitOfMass.STONES, UnitOfMass.KILOGRAMS, 0.157473),
PowerConverter: (UnitOfPower.WATT, UnitOfPower.KILO_WATT, 1000),
PressureConverter: (UnitOfPressure.HPA, UnitOfPressure.INHG, 33.86389),
ReactiveEnergyConverter: (
UnitOfReactiveEnergy.VOLT_AMPERE_REACTIVE_HOUR,
UnitOfReactiveEnergy.KILO_VOLT_AMPERE_REACTIVE_HOUR,
1000,
),
SpeedConverter: (
UnitOfSpeed.KILOMETERS_PER_HOUR,
UnitOfSpeed.MILES_PER_HOUR,
@ -622,6 +630,20 @@ _CONVERTED_VALUE: dict[
(30, UnitOfPressure.MMHG, 1.181102, UnitOfPressure.INHG),
(5, UnitOfPressure.BAR, 72.51887, UnitOfPressure.PSI),
],
ReactiveEnergyConverter: [
(
5,
UnitOfReactiveEnergy.KILO_VOLT_AMPERE_REACTIVE_HOUR,
5000,
UnitOfReactiveEnergy.VOLT_AMPERE_REACTIVE_HOUR,
),
(
5,
UnitOfReactiveEnergy.VOLT_AMPERE_REACTIVE_HOUR,
0.005,
UnitOfReactiveEnergy.KILO_VOLT_AMPERE_REACTIVE_HOUR,
),
],
SpeedConverter: [
# 5 km/h / 1.609 km/mi = 3.10686 mi/h
(5, UnitOfSpeed.KILOMETERS_PER_HOUR, 3.106856, UnitOfSpeed.MILES_PER_HOUR),