From cc4b9e0eca2436be9ae989e87edb19bbb327651a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ludovic=20BOU=C3=89?= Date: Thu, 14 Aug 2025 11:46:06 +0200 Subject: [PATCH] Extend UnitOfReactivePower with 'mvar' (#150415) --- homeassistant/components/number/const.py | 2 +- .../components/recorder/statistics.py | 2 + .../components/recorder/websocket_api.py | 2 + homeassistant/components/sensor/const.py | 4 +- homeassistant/const.py | 1 + homeassistant/util/unit_conversion.py | 17 ++++++ tests/components/sensor/test_init.py | 1 - tests/util/test_unit_conversion.py | 60 +++++++++++++++---- 8 files changed, 73 insertions(+), 16 deletions(-) diff --git a/homeassistant/components/number/const.py b/homeassistant/components/number/const.py index 373814cae9a..02e11d1530a 100644 --- a/homeassistant/components/number/const.py +++ b/homeassistant/components/number/const.py @@ -338,7 +338,7 @@ class NumberDeviceClass(StrEnum): REACTIVE_POWER = "reactive_power" """Reactive power. - Unit of measurement: `var`, `kvar` + Unit of measurement: `mvar`, `var`, `kvar` """ SIGNAL_STRENGTH = "signal_strength" diff --git a/homeassistant/components/recorder/statistics.py b/homeassistant/components/recorder/statistics.py index 20fd1a3ce28..2321da45bb9 100644 --- a/homeassistant/components/recorder/statistics.py +++ b/homeassistant/components/recorder/statistics.py @@ -60,6 +60,7 @@ from homeassistant.util.unit_conversion import ( PowerConverter, PressureConverter, ReactiveEnergyConverter, + ReactivePowerConverter, SpeedConverter, TemperatureConverter, UnitlessRatioConverter, @@ -216,6 +217,7 @@ STATISTIC_UNIT_TO_UNIT_CONVERTER: dict[str | None, type[BaseUnitConverter]] = { **dict.fromkeys(PowerConverter.VALID_UNITS, PowerConverter), **dict.fromkeys(PressureConverter.VALID_UNITS, PressureConverter), **dict.fromkeys(ReactiveEnergyConverter.VALID_UNITS, ReactiveEnergyConverter), + **dict.fromkeys(ReactivePowerConverter.VALID_UNITS, ReactivePowerConverter), **dict.fromkeys(SpeedConverter.VALID_UNITS, SpeedConverter), **dict.fromkeys(TemperatureConverter.VALID_UNITS, TemperatureConverter), **dict.fromkeys(UnitlessRatioConverter.VALID_UNITS, UnitlessRatioConverter), diff --git a/homeassistant/components/recorder/websocket_api.py b/homeassistant/components/recorder/websocket_api.py index 310e2fc85c5..4f798fb86d0 100644 --- a/homeassistant/components/recorder/websocket_api.py +++ b/homeassistant/components/recorder/websocket_api.py @@ -33,6 +33,7 @@ from homeassistant.util.unit_conversion import ( PowerConverter, PressureConverter, ReactiveEnergyConverter, + ReactivePowerConverter, SpeedConverter, TemperatureConverter, UnitlessRatioConverter, @@ -81,6 +82,7 @@ UNIT_SCHEMA = vol.Schema( vol.Optional("power"): vol.In(PowerConverter.VALID_UNITS), vol.Optional("pressure"): vol.In(PressureConverter.VALID_UNITS), vol.Optional("reactive_energy"): vol.In(ReactiveEnergyConverter.VALID_UNITS), + vol.Optional("reactive_power"): vol.In(ReactivePowerConverter.VALID_UNITS), vol.Optional("speed"): vol.In(SpeedConverter.VALID_UNITS), vol.Optional("temperature"): vol.In(TemperatureConverter.VALID_UNITS), vol.Optional("unitless"): vol.In(UnitlessRatioConverter.VALID_UNITS), diff --git a/homeassistant/components/sensor/const.py b/homeassistant/components/sensor/const.py index 251a233e1fa..92607ba07eb 100644 --- a/homeassistant/components/sensor/const.py +++ b/homeassistant/components/sensor/const.py @@ -64,6 +64,7 @@ from homeassistant.util.unit_conversion import ( PowerConverter, PressureConverter, ReactiveEnergyConverter, + ReactivePowerConverter, SpeedConverter, TemperatureConverter, UnitlessRatioConverter, @@ -370,7 +371,7 @@ class SensorDeviceClass(StrEnum): REACTIVE_POWER = "reactive_power" """Reactive power. - Unit of measurement: `var`, `kvar` + Unit of measurement: `mvar`, `var`, `kvar` """ SIGNAL_STRENGTH = "signal_strength" @@ -550,6 +551,7 @@ UNIT_CONVERTERS: dict[SensorDeviceClass | str | None, type[BaseUnitConverter]] = SensorDeviceClass.PRECIPITATION_INTENSITY: SpeedConverter, SensorDeviceClass.PRESSURE: PressureConverter, SensorDeviceClass.REACTIVE_ENERGY: ReactiveEnergyConverter, + SensorDeviceClass.REACTIVE_POWER: ReactivePowerConverter, SensorDeviceClass.SPEED: SpeedConverter, SensorDeviceClass.TEMPERATURE: TemperatureConverter, SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS: MassVolumeConcentrationConverter, diff --git a/homeassistant/const.py b/homeassistant/const.py index 8e340d8468b..b74fa64d5c7 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -609,6 +609,7 @@ class UnitOfPower(StrEnum): class UnitOfReactivePower(StrEnum): """Reactive power units.""" + MILLIVOLT_AMPERE_REACTIVE = "mvar" VOLT_AMPERE_REACTIVE = "var" KILO_VOLT_AMPERE_REACTIVE = "kvar" diff --git a/homeassistant/util/unit_conversion.py b/homeassistant/util/unit_conversion.py index 610cf5db7a9..ad459e55d15 100644 --- a/homeassistant/util/unit_conversion.py +++ b/homeassistant/util/unit_conversion.py @@ -29,6 +29,7 @@ from homeassistant.const import ( UnitOfPower, UnitOfPressure, UnitOfReactiveEnergy, + UnitOfReactivePower, UnitOfSpeed, UnitOfTemperature, UnitOfTime, @@ -460,6 +461,22 @@ class ReactiveEnergyConverter(BaseUnitConverter): VALID_UNITS = set(UnitOfReactiveEnergy) +class ReactivePowerConverter(BaseUnitConverter): + """Utility to convert reactive power values.""" + + UNIT_CLASS = "reactive_power" + _UNIT_CONVERSION: dict[str | None, float] = { + UnitOfReactivePower.MILLIVOLT_AMPERE_REACTIVE: 1 * 1000, + UnitOfReactivePower.VOLT_AMPERE_REACTIVE: 1, + UnitOfReactivePower.KILO_VOLT_AMPERE_REACTIVE: 1 / 1000, + } + VALID_UNITS = { + UnitOfReactivePower.MILLIVOLT_AMPERE_REACTIVE, + UnitOfReactivePower.VOLT_AMPERE_REACTIVE, + UnitOfReactivePower.KILO_VOLT_AMPERE_REACTIVE, + } + + class SpeedConverter(BaseUnitConverter): """Utility to convert speed values.""" diff --git a/tests/components/sensor/test_init.py b/tests/components/sensor/test_init.py index ce21f6ea8ab..62141186b55 100644 --- a/tests/components/sensor/test_init.py +++ b/tests/components/sensor/test_init.py @@ -2978,7 +2978,6 @@ def test_device_class_converters_are_complete() -> None: SensorDeviceClass.PM1, SensorDeviceClass.PM10, SensorDeviceClass.PM25, - SensorDeviceClass.REACTIVE_POWER, SensorDeviceClass.SIGNAL_STRENGTH, SensorDeviceClass.SOUND_PRESSURE, SensorDeviceClass.SULPHUR_DIOXIDE, diff --git a/tests/util/test_unit_conversion.py b/tests/util/test_unit_conversion.py index 1ef66584952..08fb7cce067 100644 --- a/tests/util/test_unit_conversion.py +++ b/tests/util/test_unit_conversion.py @@ -29,6 +29,7 @@ from homeassistant.const import ( UnitOfPower, UnitOfPressure, UnitOfReactiveEnergy, + UnitOfReactivePower, UnitOfSpeed, UnitOfTemperature, UnitOfTime, @@ -57,6 +58,7 @@ from homeassistant.util.unit_conversion import ( PowerConverter, PressureConverter, ReactiveEnergyConverter, + ReactivePowerConverter, SpeedConverter, TemperatureConverter, UnitlessRatioConverter, @@ -89,6 +91,7 @@ _ALL_CONVERTERS: dict[type[BaseUnitConverter], list[str | None]] = { PowerConverter, PressureConverter, ReactiveEnergyConverter, + ReactivePowerConverter, SpeedConverter, TemperatureConverter, UnitlessRatioConverter, @@ -100,6 +103,11 @@ _ALL_CONVERTERS: dict[type[BaseUnitConverter], list[str | None]] = { # Dict containing all converters with a corresponding unit ratio. _GET_UNIT_RATIO: dict[type[BaseUnitConverter], tuple[str | None, str | None, float]] = { + ApparentPowerConverter: ( + UnitOfApparentPower.MILLIVOLT_AMPERE, + UnitOfApparentPower.VOLT_AMPERE, + 1000, + ), AreaConverter: (UnitOfArea.SQUARE_KILOMETERS, UnitOfArea.SQUARE_METERS, 0.000001), BloodGlucoseConcentrationConverter: ( UnitOfBloodGlucoseConcentration.MILLIGRAMS_PER_DECILITER, @@ -141,11 +149,6 @@ _GET_UNIT_RATIO: dict[type[BaseUnitConverter], tuple[str | None, str | None, flo CONCENTRATION_MILLIGRAMS_PER_CUBIC_METER, 1000, ), - ApparentPowerConverter: ( - UnitOfApparentPower.MILLIVOLT_AMPERE, - UnitOfApparentPower.VOLT_AMPERE, - 1000, - ), PowerConverter: (UnitOfPower.WATT, UnitOfPower.KILO_WATT, 1000), PressureConverter: (UnitOfPressure.HPA, UnitOfPressure.INHG, 33.86389), ReactiveEnergyConverter: ( @@ -153,6 +156,11 @@ _GET_UNIT_RATIO: dict[type[BaseUnitConverter], tuple[str | None, str | None, flo UnitOfReactiveEnergy.KILO_VOLT_AMPERE_REACTIVE_HOUR, 1000, ), + ReactivePowerConverter: ( + UnitOfReactivePower.MILLIVOLT_AMPERE_REACTIVE, + UnitOfReactivePower.VOLT_AMPERE_REACTIVE, + 1000, + ), SpeedConverter: ( UnitOfSpeed.KILOMETERS_PER_HOUR, UnitOfSpeed.MILES_PER_HOUR, @@ -176,6 +184,14 @@ _GET_UNIT_RATIO: dict[type[BaseUnitConverter], tuple[str | None, str | None, flo _CONVERTED_VALUE: dict[ type[BaseUnitConverter], list[tuple[float, str | None, float, str | None]] ] = { + ApparentPowerConverter: [ + ( + 10, + UnitOfApparentPower.MILLIVOLT_AMPERE, + 0.01, + UnitOfApparentPower.VOLT_AMPERE, + ), + ], AreaConverter: [ # Square Meters to other units (5, UnitOfArea.SQUARE_METERS, 50000, UnitOfArea.SQUARE_CENTIMETERS), @@ -623,14 +639,6 @@ _CONVERTED_VALUE: dict[ (1, UnitOfMass.STONES, 14, UnitOfMass.POUNDS), (1, UnitOfMass.STONES, 224, UnitOfMass.OUNCES), ], - ApparentPowerConverter: [ - ( - 10, - UnitOfApparentPower.MILLIVOLT_AMPERE, - 0.01, - UnitOfApparentPower.VOLT_AMPERE, - ), - ], PowerConverter: [ (10, UnitOfPower.KILO_WATT, 10000, UnitOfPower.WATT), (10, UnitOfPower.MEGA_WATT, 10e6, UnitOfPower.WATT), @@ -682,6 +690,32 @@ _CONVERTED_VALUE: dict[ UnitOfReactiveEnergy.KILO_VOLT_AMPERE_REACTIVE_HOUR, ), ], + ReactivePowerConverter: [ + ( + 10, + UnitOfReactivePower.KILO_VOLT_AMPERE_REACTIVE, + 10000, + UnitOfReactivePower.VOLT_AMPERE_REACTIVE, + ), + ( + 10, + UnitOfReactivePower.VOLT_AMPERE_REACTIVE, + 0.01, + UnitOfReactivePower.KILO_VOLT_AMPERE_REACTIVE, + ), + ( + 10, + UnitOfReactivePower.MILLIVOLT_AMPERE_REACTIVE, + 0.01, + UnitOfReactivePower.VOLT_AMPERE_REACTIVE, + ), + ( + 10, + UnitOfReactivePower.MILLIVOLT_AMPERE_REACTIVE, + 0.00001, + UnitOfReactivePower.KILO_VOLT_AMPERE_REACTIVE, + ), + ], SpeedConverter: [ # 5 km/h / 1.609 km/mi = 3.10686 mi/h (5, UnitOfSpeed.KILOMETERS_PER_HOUR, 3.106856, UnitOfSpeed.MILES_PER_HOUR),