From 12e4d18038559d1df75ddc43a4ab79a219dab2be Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Tue, 27 Sep 2022 18:37:52 +0100 Subject: [PATCH] Add volume to SensorDeviceClass (#77960) * Add volume to SensorDeviceClass * Adjust recorder * Adjust tests * Adjust sensor UNIT_CONVERTERS * Adjust recorder * Update strings.json --- .../components/recorder/websocket_api.py | 10 ++++--- homeassistant/components/sensor/__init__.py | 6 ++++ .../components/sensor/device_condition.py | 4 ++- .../components/sensor/device_trigger.py | 4 ++- homeassistant/components/sensor/recorder.py | 3 +- homeassistant/components/sensor/strings.json | 22 +++++++------- .../components/sensor/translations/en.json | 6 ++-- .../components/recorder/test_websocket_api.py | 30 +++++++++++++++++++ tests/components/sensor/test_init.py | 29 ++++++++++++++++++ tests/components/sensor/test_recorder.py | 22 +++++++++++--- 10 files changed, 113 insertions(+), 23 deletions(-) diff --git a/homeassistant/components/recorder/websocket_api.py b/homeassistant/components/recorder/websocket_api.py index 2ede11cf887..5feb51000fb 100644 --- a/homeassistant/components/recorder/websocket_api.py +++ b/homeassistant/components/recorder/websocket_api.py @@ -13,8 +13,6 @@ from homeassistant.const import ( ENERGY_KILO_WATT_HOUR, ENERGY_MEGA_WATT_HOUR, ENERGY_WATT_HOUR, - VOLUME_CUBIC_FEET, - VOLUME_CUBIC_METERS, ) from homeassistant.core import HomeAssistant, callback, valid_entity_id from homeassistant.helpers import config_validation as cv @@ -27,6 +25,7 @@ from homeassistant.util.unit_conversion import ( PressureConverter, SpeedConverter, TemperatureConverter, + VolumeConverter, ) from .const import MAX_QUEUE_BACKLOG @@ -131,7 +130,7 @@ async def ws_handle_get_statistics_during_period( vol.Optional("pressure"): vol.In(PressureConverter.VALID_UNITS), vol.Optional("speed"): vol.In(SpeedConverter.VALID_UNITS), vol.Optional("temperature"): vol.In(TemperatureConverter.VALID_UNITS), - vol.Optional("volume"): vol.Any(VOLUME_CUBIC_FEET, VOLUME_CUBIC_METERS), + vol.Optional("volume"): vol.In(VolumeConverter.VALID_UNITS), } ), } @@ -336,7 +335,10 @@ async def ws_adjust_sum_statistics( ENERGY_WATT_HOUR, ): return True - if statistics_unit == VOLUME_CUBIC_METERS and display_unit == VOLUME_CUBIC_FEET: + if ( + statistics_unit == VolumeConverter.NORMALIZED_UNIT + and display_unit in VolumeConverter.VALID_UNITS + ): return True return False diff --git a/homeassistant/components/sensor/__init__.py b/homeassistant/components/sensor/__init__.py index 1babc2d0084..c7b8e9a4940 100644 --- a/homeassistant/components/sensor/__init__.py +++ b/homeassistant/components/sensor/__init__.py @@ -64,6 +64,7 @@ from homeassistant.util.unit_conversion import ( PressureConverter, SpeedConverter, TemperatureConverter, + VolumeConverter, ) from .const import CONF_STATE_CLASS # noqa: F401 @@ -185,6 +186,9 @@ class SensorDeviceClass(StrEnum): # voltage (V) VOLTAGE = "voltage" + # volume (VOLUME_*) + VOLUME = "volume" + DEVICE_CLASSES_SCHEMA: Final = vol.All(vol.Lower, vol.Coerce(SensorDeviceClass)) @@ -221,6 +225,7 @@ UNIT_CONVERTERS: dict[str, type[BaseUnitConverter]] = { SensorDeviceClass.PRESSURE: PressureConverter, SensorDeviceClass.SPEED: SpeedConverter, SensorDeviceClass.TEMPERATURE: TemperatureConverter, + SensorDeviceClass.VOLUME: VolumeConverter, } UNIT_RATIOS: dict[str, dict[str, float]] = { @@ -232,6 +237,7 @@ UNIT_RATIOS: dict[str, dict[str, float]] = { TEMP_FAHRENHEIT: 1.8, TEMP_KELVIN: 1.0, }, + SensorDeviceClass.VOLUME: VolumeConverter.UNIT_CONVERSION, } # mypy: disallow-any-generics diff --git a/homeassistant/components/sensor/device_condition.py b/homeassistant/components/sensor/device_condition.py index 08aeda46ba2..72ef4a62c48 100644 --- a/homeassistant/components/sensor/device_condition.py +++ b/homeassistant/components/sensor/device_condition.py @@ -58,9 +58,10 @@ CONF_IS_REACTIVE_POWER = "is_reactive_power" CONF_IS_SIGNAL_STRENGTH = "is_signal_strength" CONF_IS_SULPHUR_DIOXIDE = "is_sulphur_dioxide" CONF_IS_TEMPERATURE = "is_temperature" +CONF_IS_VALUE = "is_value" CONF_IS_VOLATILE_ORGANIC_COMPOUNDS = "is_volatile_organic_compounds" CONF_IS_VOLTAGE = "is_voltage" -CONF_IS_VALUE = "is_value" +CONF_IS_VOLUME = "is_volume" ENTITY_CONDITIONS = { SensorDeviceClass.APPARENT_POWER: [{CONF_TYPE: CONF_IS_APPARENT_POWER}], @@ -94,6 +95,7 @@ ENTITY_CONDITIONS = { {CONF_TYPE: CONF_IS_VOLATILE_ORGANIC_COMPOUNDS} ], SensorDeviceClass.VOLTAGE: [{CONF_TYPE: CONF_IS_VOLTAGE}], + SensorDeviceClass.VOLUME: [{CONF_TYPE: CONF_IS_VOLUME}], DEVICE_CLASS_NONE: [{CONF_TYPE: CONF_IS_VALUE}], } diff --git a/homeassistant/components/sensor/device_trigger.py b/homeassistant/components/sensor/device_trigger.py index a1275b202ed..a2b92186410 100644 --- a/homeassistant/components/sensor/device_trigger.py +++ b/homeassistant/components/sensor/device_trigger.py @@ -57,9 +57,10 @@ CONF_SIGNAL_STRENGTH = "signal_strength" CONF_SPEED = "speed" CONF_SULPHUR_DIOXIDE = "sulphur_dioxide" CONF_TEMPERATURE = "temperature" +CONF_VALUE = "value" CONF_VOLATILE_ORGANIC_COMPOUNDS = "volatile_organic_compounds" CONF_VOLTAGE = "voltage" -CONF_VALUE = "value" +CONF_VOLUME = "volume" ENTITY_TRIGGERS = { SensorDeviceClass.APPARENT_POWER: [{CONF_TYPE: CONF_APPARENT_POWER}], @@ -93,6 +94,7 @@ ENTITY_TRIGGERS = { {CONF_TYPE: CONF_VOLATILE_ORGANIC_COMPOUNDS} ], SensorDeviceClass.VOLTAGE: [{CONF_TYPE: CONF_VOLTAGE}], + SensorDeviceClass.VOLUME: [{CONF_TYPE: CONF_VOLUME}], DEVICE_CLASS_NONE: [{CONF_TYPE: CONF_VALUE}], } diff --git a/homeassistant/components/sensor/recorder.py b/homeassistant/components/sensor/recorder.py index d7c8ae38c5c..564a5226f58 100644 --- a/homeassistant/components/sensor/recorder.py +++ b/homeassistant/components/sensor/recorder.py @@ -61,11 +61,12 @@ DEFAULT_STATISTICS = { UNIT_CONVERTERS: dict[str, type[BaseUnitConverter]] = { SensorDeviceClass.DISTANCE: DistanceConverter, SensorDeviceClass.ENERGY: EnergyConverter, + SensorDeviceClass.GAS: VolumeConverter, SensorDeviceClass.POWER: PowerConverter, SensorDeviceClass.PRESSURE: PressureConverter, SensorDeviceClass.SPEED: SpeedConverter, SensorDeviceClass.TEMPERATURE: TemperatureConverter, - SensorDeviceClass.GAS: VolumeConverter, + SensorDeviceClass.VOLUME: VolumeConverter, } # Keep track of entities for which a warning about decreasing value has been logged diff --git a/homeassistant/components/sensor/strings.json b/homeassistant/components/sensor/strings.json index 6a371177321..affc1a8e3e9 100644 --- a/homeassistant/components/sensor/strings.json +++ b/homeassistant/components/sensor/strings.json @@ -6,7 +6,10 @@ "is_battery_level": "Current {entity_name} battery level", "is_carbon_monoxide": "Current {entity_name} carbon monoxide concentration level", "is_carbon_dioxide": "Current {entity_name} carbon dioxide concentration level", + "is_current": "Current {entity_name} current", "is_distance": "Current {entity_name} distance", + "is_energy": "Current {entity_name} energy", + "is_frequency": "Current {entity_name} frequency", "is_gas": "Current {entity_name} gas", "is_humidity": "Current {entity_name} humidity", "is_illuminance": "Current {entity_name} illuminance", @@ -19,26 +22,27 @@ "is_pm10": "Current {entity_name} PM10 concentration level", "is_pm25": "Current {entity_name} PM2.5 concentration level", "is_power": "Current {entity_name} power", + "is_power_factor": "Current {entity_name} power factor", "is_pressure": "Current {entity_name} pressure", "is_reactive_power": "Current {entity_name} reactive power", "is_signal_strength": "Current {entity_name} signal strength", "is_speed": "Current {entity_name} speed", "is_sulphur_dioxide": "Current {entity_name} sulphur dioxide concentration level", "is_temperature": "Current {entity_name} temperature", - "is_current": "Current {entity_name} current", - "is_energy": "Current {entity_name} energy", - "is_frequency": "Current {entity_name} frequency", - "is_power_factor": "Current {entity_name} power factor", + "is_value": "Current {entity_name} value", "is_volatile_organic_compounds": "Current {entity_name} volatile organic compounds concentration level", "is_voltage": "Current {entity_name} voltage", - "is_value": "Current {entity_name} value" + "is_volume": "Current {entity_name} volume" }, "trigger_type": { "apparent_power": "{entity_name} apparent power changes", "battery_level": "{entity_name} battery level changes", "carbon_monoxide": "{entity_name} carbon monoxide concentration changes", "carbon_dioxide": "{entity_name} carbon dioxide concentration changes", + "current": "{entity_name} current changes", "distance": "{entity_name} distance changes", + "energy": "{entity_name} energy changes", + "frequency": "{entity_name} frequency changes", "gas": "{entity_name} gas changes", "humidity": "{entity_name} humidity changes", "illuminance": "{entity_name} illuminance changes", @@ -51,19 +55,17 @@ "pm10": "{entity_name} PM10 concentration changes", "pm25": "{entity_name} PM2.5 concentration changes", "power": "{entity_name} power changes", + "power_factor": "{entity_name} power factor changes", "pressure": "{entity_name} pressure changes", "reactive_power": "{entity_name} reactive power changes", "signal_strength": "{entity_name} signal strength changes", "speed": "{entity_name} speed changes", "sulphur_dioxide": "{entity_name} sulphur dioxide concentration changes", "temperature": "{entity_name} temperature changes", - "current": "{entity_name} current changes", - "energy": "{entity_name} energy changes", - "frequency": "{entity_name} frequency changes", - "power_factor": "{entity_name} power factor changes", + "value": "{entity_name} value changes", "volatile_organic_compounds": "{entity_name} volatile organic compounds concentration changes", "voltage": "{entity_name} voltage changes", - "value": "{entity_name} value changes" + "volume": "{entity_name} volume changes" } }, "state": { diff --git a/homeassistant/components/sensor/translations/en.json b/homeassistant/components/sensor/translations/en.json index 0c4af7c1c32..fd801bd1416 100644 --- a/homeassistant/components/sensor/translations/en.json +++ b/homeassistant/components/sensor/translations/en.json @@ -30,7 +30,8 @@ "is_temperature": "Current {entity_name} temperature", "is_value": "Current {entity_name} value", "is_volatile_organic_compounds": "Current {entity_name} volatile organic compounds concentration level", - "is_voltage": "Current {entity_name} voltage" + "is_voltage": "Current {entity_name} voltage", + "is_volume": "Current {entity_name} volume" }, "trigger_type": { "apparent_power": "{entity_name} apparent power changes", @@ -62,7 +63,8 @@ "temperature": "{entity_name} temperature changes", "value": "{entity_name} value changes", "volatile_organic_compounds": "{entity_name} volatile organic compounds concentration changes", - "voltage": "{entity_name} voltage changes" + "voltage": "{entity_name} voltage changes", + "volume": "{entity_name} volume changes" } }, "state": { diff --git a/tests/components/recorder/test_websocket_api.py b/tests/components/recorder/test_websocket_api.py index 095b5c15d27..e8d8093e131 100644 --- a/tests/components/recorder/test_websocket_api.py +++ b/tests/components/recorder/test_websocket_api.py @@ -100,6 +100,26 @@ TEMPERATURE_SENSOR_F_ATTRIBUTES = { "state_class": "measurement", "unit_of_measurement": "°F", } +VOLUME_SENSOR_FT3_ATTRIBUTES = { + "device_class": "volume", + "state_class": "measurement", + "unit_of_measurement": "ft³", +} +VOLUME_SENSOR_M3_ATTRIBUTES = { + "device_class": "volume", + "state_class": "measurement", + "unit_of_measurement": "m³", +} +VOLUME_SENSOR_FT3_ATTRIBUTES_TOTAL = { + "device_class": "volume", + "state_class": "total", + "unit_of_measurement": "ft³", +} +VOLUME_SENSOR_M3_ATTRIBUTES_TOTAL = { + "device_class": "volume", + "state_class": "total", + "unit_of_measurement": "m³", +} async def test_statistics_during_period(hass, hass_ws_client, recorder_mock): @@ -175,6 +195,8 @@ async def test_statistics_during_period(hass, hass_ws_client, recorder_mock): (TEMPERATURE_SENSOR_C_ATTRIBUTES, 10, 10, {"temperature": "°C"}, 10), (TEMPERATURE_SENSOR_C_ATTRIBUTES, 10, 10, {"temperature": "°F"}, 50), (TEMPERATURE_SENSOR_C_ATTRIBUTES, 10, 10, {"temperature": "K"}, 283.15), + (VOLUME_SENSOR_M3_ATTRIBUTES, 10, 10, {"volume": "m³"}, 10), + (VOLUME_SENSOR_M3_ATTRIBUTES, 10, 10, {"volume": "ft³"}, 353.14666), ], ) async def test_statistics_during_period_unit_conversion( @@ -266,6 +288,8 @@ async def test_statistics_during_period_unit_conversion( (ENERGY_SENSOR_KWH_ATTRIBUTES, 10, 10, {"energy": "Wh"}, 10000), (GAS_SENSOR_M3_ATTRIBUTES, 10, 10, {"volume": "m³"}, 10), (GAS_SENSOR_M3_ATTRIBUTES, 10, 10, {"volume": "ft³"}, 353.147), + (VOLUME_SENSOR_M3_ATTRIBUTES_TOTAL, 10, 10, {"volume": "m³"}, 10), + (VOLUME_SENSOR_M3_ATTRIBUTES_TOTAL, 10, 10, {"volume": "ft³"}, 353.147), ], ) async def test_sum_statistics_during_period_unit_conversion( @@ -583,6 +607,10 @@ async def test_statistics_during_period_bad_end_time( (METRIC_SYSTEM, TEMPERATURE_SENSOR_C_ATTRIBUTES, "°C", "°C", "temperature"), (IMPERIAL_SYSTEM, TEMPERATURE_SENSOR_F_ATTRIBUTES, "°F", "°C", "temperature"), (METRIC_SYSTEM, TEMPERATURE_SENSOR_F_ATTRIBUTES, "°F", "°C", "temperature"), + (IMPERIAL_SYSTEM, VOLUME_SENSOR_FT3_ATTRIBUTES, "ft³", "m³", "volume"), + (METRIC_SYSTEM, VOLUME_SENSOR_FT3_ATTRIBUTES, "ft³", "m³", "volume"), + (IMPERIAL_SYSTEM, VOLUME_SENSOR_FT3_ATTRIBUTES_TOTAL, "ft³", "m³", "volume"), + (METRIC_SYSTEM, VOLUME_SENSOR_FT3_ATTRIBUTES_TOTAL, "ft³", "m³", "volume"), ], ) async def test_list_statistic_ids( @@ -1376,6 +1404,8 @@ async def test_backup_end_without_start( (METRIC_SYSTEM, SPEED_SENSOR_MPH_ATTRIBUTES, "m/s", "speed"), (METRIC_SYSTEM, TEMPERATURE_SENSOR_C_ATTRIBUTES, "°C", "temperature"), (METRIC_SYSTEM, TEMPERATURE_SENSOR_F_ATTRIBUTES, "°C", "temperature"), + (METRIC_SYSTEM, VOLUME_SENSOR_FT3_ATTRIBUTES, "m³", "volume"), + (METRIC_SYSTEM, VOLUME_SENSOR_M3_ATTRIBUTES, "m³", "volume"), ], ) async def test_get_statistics_metadata( diff --git a/tests/components/sensor/test_init.py b/tests/components/sensor/test_init.py index 47cfc0ef148..f9b90dc5bd5 100644 --- a/tests/components/sensor/test_init.py +++ b/tests/components/sensor/test_init.py @@ -23,6 +23,10 @@ from homeassistant.const import ( STATE_UNKNOWN, TEMP_CELSIUS, TEMP_FAHRENHEIT, + VOLUME_CUBIC_FEET, + VOLUME_CUBIC_METERS, + VOLUME_FLUID_OUNCE, + VOLUME_LITERS, ) from homeassistant.core import State from homeassistant.helpers import entity_registry as er @@ -549,6 +553,31 @@ async def test_custom_unit( 100, SensorDeviceClass.SPEED, ), + # Volume + ( + VOLUME_CUBIC_METERS, + VOLUME_CUBIC_FEET, + VOLUME_CUBIC_FEET, + 100, + 3531, + SensorDeviceClass.VOLUME, + ), + ( + VOLUME_FLUID_OUNCE, + VOLUME_LITERS, + VOLUME_LITERS, + 78, + 2.3, + SensorDeviceClass.VOLUME, + ), + ( + VOLUME_CUBIC_METERS, + "peer_distance", + VOLUME_CUBIC_METERS, + 100, + 100, + SensorDeviceClass.VOLUME, + ), ], ) async def test_custom_unit_change( diff --git a/tests/components/sensor/test_recorder.py b/tests/components/sensor/test_recorder.py index ad11206f583..3d4f48360fc 100644 --- a/tests/components/sensor/test_recorder.py +++ b/tests/components/sensor/test_recorder.py @@ -98,6 +98,8 @@ def set_time_zone(): ("speed", "mph", "mph", "m/s", "speed", 13.050847, -10, 30), ("temperature", "°C", "°C", "°C", "temperature", 13.050847, -10, 30), ("temperature", "°F", "°F", "°C", "temperature", 13.050847, -10, 30), + ("volume", "m³", "m³", "m³", "volume", 13.050847, -10, 30), + ("volume", "ft³", "ft³", "m³", "volume", 13.050847, -10, 30), ], ) def test_compile_hourly_statistics( @@ -359,18 +361,22 @@ def test_compile_hourly_statistics_unsupported(hass_recorder, caplog, attributes (IMPERIAL_SYSTEM, "distance", "mi", "mi", "m", "distance", 1), (IMPERIAL_SYSTEM, "energy", "kWh", "kWh", "kWh", "energy", 1), (IMPERIAL_SYSTEM, "energy", "Wh", "Wh", "kWh", "energy", 1), - (IMPERIAL_SYSTEM, "monetary", "EUR", "EUR", "EUR", None, 1), - (IMPERIAL_SYSTEM, "monetary", "SEK", "SEK", "SEK", None, 1), (IMPERIAL_SYSTEM, "gas", "m³", "m³", "m³", "volume", 1), (IMPERIAL_SYSTEM, "gas", "ft³", "ft³", "m³", "volume", 1), + (IMPERIAL_SYSTEM, "monetary", "EUR", "EUR", "EUR", None, 1), + (IMPERIAL_SYSTEM, "monetary", "SEK", "SEK", "SEK", None, 1), + (IMPERIAL_SYSTEM, "volume", "m³", "m³", "m³", "volume", 1), + (IMPERIAL_SYSTEM, "volume", "ft³", "ft³", "m³", "volume", 1), (METRIC_SYSTEM, "distance", "m", "m", "m", "distance", 1), (METRIC_SYSTEM, "distance", "mi", "mi", "m", "distance", 1), (METRIC_SYSTEM, "energy", "kWh", "kWh", "kWh", "energy", 1), (METRIC_SYSTEM, "energy", "Wh", "Wh", "kWh", "energy", 1), - (METRIC_SYSTEM, "monetary", "EUR", "EUR", "EUR", None, 1), - (METRIC_SYSTEM, "monetary", "SEK", "SEK", "SEK", None, 1), (METRIC_SYSTEM, "gas", "m³", "m³", "m³", "volume", 1), (METRIC_SYSTEM, "gas", "ft³", "ft³", "m³", "volume", 1), + (METRIC_SYSTEM, "monetary", "EUR", "EUR", "EUR", None, 1), + (METRIC_SYSTEM, "monetary", "SEK", "SEK", "SEK", None, 1), + (METRIC_SYSTEM, "volume", "m³", "m³", "m³", "volume", 1), + (METRIC_SYSTEM, "volume", "ft³", "ft³", "m³", "volume", 1), ], ) async def test_compile_hourly_sum_statistics_amount( @@ -1569,6 +1575,8 @@ def test_compile_hourly_energy_statistics_multiple(hass_recorder, caplog): ("speed", "mph", 30), ("temperature", "°C", 30), ("temperature", "°F", 30), + ("volume", "m³", 30), + ("volume", "ft³", 30), ], ) def test_compile_hourly_statistics_unchanged( @@ -1660,6 +1668,8 @@ def test_compile_hourly_statistics_partially_unavailable(hass_recorder, caplog): ("speed", "mph", 30), ("temperature", "°C", 30), ("temperature", "°F", 30), + ("volume", "m³", 30), + ("volume", "ft³", 30), ], ) def test_compile_hourly_statistics_unavailable( @@ -1751,6 +1761,10 @@ def test_compile_hourly_statistics_fails(hass_recorder, caplog): ("measurement", "speed", "mph", "mph", "m/s", "speed", "mean"), ("measurement", "temperature", "°C", "°C", "°C", "temperature", "mean"), ("measurement", "temperature", "°F", "°F", "°C", "temperature", "mean"), + ("measurement", "volume", "m³", "m³", "m³", "volume", "mean"), + ("measurement", "volume", "ft³", "ft³", "m³", "volume", "mean"), + ("total", "volume", "m³", "m³", "m³", "volume", "sum"), + ("total", "volume", "ft³", "ft³", "m³", "volume", "sum"), ], ) def test_list_statistic_ids(