diff --git a/homeassistant/components/energy/sensor.py b/homeassistant/components/energy/sensor.py index 1105e6f6b86..5aa710be19e 100644 --- a/homeassistant/components/energy/sensor.py +++ b/homeassistant/components/energy/sensor.py @@ -48,6 +48,7 @@ VALID_ENERGY_UNITS_GAS = { UnitOfVolume.CUBIC_FEET, UnitOfVolume.CUBIC_METERS, UnitOfVolume.LITERS, + UnitOfVolume.MILLE_CUBIC_FEET, *VALID_ENERGY_UNITS, } VALID_VOLUME_UNITS_WATER: set[str] = { @@ -56,6 +57,7 @@ VALID_VOLUME_UNITS_WATER: set[str] = { UnitOfVolume.CUBIC_METERS, UnitOfVolume.GALLONS, UnitOfVolume.LITERS, + UnitOfVolume.MILLE_CUBIC_FEET, } _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/energy/validate.py b/homeassistant/components/energy/validate.py index 3590ee9e848..6c11c2b068c 100644 --- a/homeassistant/components/energy/validate.py +++ b/homeassistant/components/energy/validate.py @@ -42,6 +42,7 @@ GAS_USAGE_UNITS: dict[str, tuple[UnitOfEnergy | UnitOfVolume, ...]] = { UnitOfVolume.CUBIC_FEET, UnitOfVolume.CUBIC_METERS, UnitOfVolume.LITERS, + UnitOfVolume.MILLE_CUBIC_FEET, ), } GAS_PRICE_UNITS = tuple( @@ -57,6 +58,7 @@ WATER_USAGE_UNITS: dict[str, tuple[UnitOfVolume, ...]] = { UnitOfVolume.CUBIC_METERS, UnitOfVolume.GALLONS, UnitOfVolume.LITERS, + UnitOfVolume.MILLE_CUBIC_FEET, ), } WATER_PRICE_UNITS = tuple( diff --git a/homeassistant/components/number/const.py b/homeassistant/components/number/const.py index 22c1170b6b8..a9333212fa4 100644 --- a/homeassistant/components/number/const.py +++ b/homeassistant/components/number/const.py @@ -207,7 +207,7 @@ class NumberDeviceClass(StrEnum): Unit of measurement: - SI / metric: `L`, `m³` - - USCS / imperial: `ft³`, `CCF` + - USCS / imperial: `ft³`, `CCF`, `MCF` """ HUMIDITY = "humidity" @@ -398,7 +398,7 @@ class NumberDeviceClass(StrEnum): Unit of measurement: `VOLUME_*` units - SI / metric: `mL`, `L`, `m³` - - USCS / imperial: `ft³`, `CCF`, `fl. oz.`, `gal` (warning: volumes expressed in + - USCS / imperial: `ft³`, `CCF`, `MCF`, `fl. oz.`, `gal` (warning: volumes expressed in USCS/imperial units are currently assumed to be US volumes) """ @@ -410,7 +410,7 @@ class NumberDeviceClass(StrEnum): Unit of measurement: `VOLUME_*` units - SI / metric: `mL`, `L`, `m³` - - USCS / imperial: `ft³`, `CCF`, `fl. oz.`, `gal` (warning: volumes expressed in + - USCS / imperial: `ft³`, `CCF`, `MCF`, `fl. oz.`, `gal` (warning: volumes expressed in USCS/imperial units are currently assumed to be US volumes) """ @@ -427,7 +427,7 @@ class NumberDeviceClass(StrEnum): Unit of measurement: - SI / metric: `m³`, `L` - - USCS / imperial: `ft³`, `CCF`, `gal` (warning: volumes expressed in + - USCS / imperial: `ft³`, `CCF`, `MCF`, `gal` (warning: volumes expressed in USCS/imperial units are currently assumed to be US volumes) """ @@ -493,6 +493,7 @@ DEVICE_CLASS_UNITS: dict[NumberDeviceClass, set[type[StrEnum] | str | None]] = { UnitOfVolume.CUBIC_FEET, UnitOfVolume.CUBIC_METERS, UnitOfVolume.LITERS, + UnitOfVolume.MILLE_CUBIC_FEET, }, NumberDeviceClass.HUMIDITY: {PERCENTAGE}, NumberDeviceClass.ILLUMINANCE: {LIGHT_LUX}, @@ -546,6 +547,7 @@ DEVICE_CLASS_UNITS: dict[NumberDeviceClass, set[type[StrEnum] | str | None]] = { UnitOfVolume.CUBIC_METERS, UnitOfVolume.GALLONS, UnitOfVolume.LITERS, + UnitOfVolume.MILLE_CUBIC_FEET, }, NumberDeviceClass.WEIGHT: set(UnitOfMass), NumberDeviceClass.WIND_DIRECTION: {DEGREE}, diff --git a/homeassistant/components/sensor/const.py b/homeassistant/components/sensor/const.py index 94578a6f652..12d9595d059 100644 --- a/homeassistant/components/sensor/const.py +++ b/homeassistant/components/sensor/const.py @@ -240,7 +240,7 @@ class SensorDeviceClass(StrEnum): Unit of measurement: - SI / metric: `L`, `m³` - - USCS / imperial: `ft³`, `CCF` + - USCS / imperial: `ft³`, `CCF`, `MCF` """ HUMIDITY = "humidity" @@ -432,7 +432,7 @@ class SensorDeviceClass(StrEnum): Unit of measurement: `VOLUME_*` units - SI / metric: `mL`, `L`, `m³` - - USCS / imperial: `ft³`, `CCF`, `fl. oz.`, `gal` (warning: volumes expressed in + - USCS / imperial: `ft³`, `CCF`, `MCF`, `fl. oz.`, `gal` (warning: volumes expressed in USCS/imperial units are currently assumed to be US volumes) """ @@ -444,7 +444,7 @@ class SensorDeviceClass(StrEnum): Unit of measurement: `VOLUME_*` units - SI / metric: `mL`, `L`, `m³` - - USCS / imperial: `ft³`, `CCF`, `fl. oz.`, `gal` (warning: volumes expressed in + - USCS / imperial: `ft³`, `CCF`, `MCF`, `fl. oz.`, `gal` (warning: volumes expressed in USCS/imperial units are currently assumed to be US volumes) """ @@ -461,7 +461,7 @@ class SensorDeviceClass(StrEnum): Unit of measurement: - SI / metric: `m³`, `L` - - USCS / imperial: `ft³`, `CCF`, `gal` (warning: volumes expressed in + - USCS / imperial: `ft³`, `CCF`, `MCF`, `gal` (warning: volumes expressed in USCS/imperial units are currently assumed to be US volumes) """ @@ -601,6 +601,7 @@ DEVICE_CLASS_UNITS: dict[SensorDeviceClass, set[type[StrEnum] | str | None]] = { UnitOfVolume.CUBIC_FEET, UnitOfVolume.CUBIC_METERS, UnitOfVolume.LITERS, + UnitOfVolume.MILLE_CUBIC_FEET, }, SensorDeviceClass.HUMIDITY: {PERCENTAGE}, SensorDeviceClass.ILLUMINANCE: {LIGHT_LUX}, @@ -654,6 +655,7 @@ DEVICE_CLASS_UNITS: dict[SensorDeviceClass, set[type[StrEnum] | str | None]] = { UnitOfVolume.CUBIC_METERS, UnitOfVolume.GALLONS, UnitOfVolume.LITERS, + UnitOfVolume.MILLE_CUBIC_FEET, }, SensorDeviceClass.WEIGHT: set(UnitOfMass), SensorDeviceClass.WIND_DIRECTION: {DEGREE}, diff --git a/homeassistant/const.py b/homeassistant/const.py index 3bd7cc51c7c..d61945e2ef8 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -766,6 +766,7 @@ class UnitOfVolume(StrEnum): CUBIC_FEET = "ft³" CENTUM_CUBIC_FEET = "CCF" + MILLE_CUBIC_FEET = "MCF" CUBIC_METERS = "m³" LITERS = "L" MILLILITERS = "mL" diff --git a/homeassistant/util/unit_conversion.py b/homeassistant/util/unit_conversion.py index 918b45ff3c9..1bd40a12d3d 100644 --- a/homeassistant/util/unit_conversion.py +++ b/homeassistant/util/unit_conversion.py @@ -752,6 +752,7 @@ class VolumeConverter(BaseUnitConverter): UnitOfVolume.CUBIC_METERS: 1, UnitOfVolume.CUBIC_FEET: 1 / _CUBIC_FOOT_TO_CUBIC_METER, UnitOfVolume.CENTUM_CUBIC_FEET: 1 / (100 * _CUBIC_FOOT_TO_CUBIC_METER), + UnitOfVolume.MILLE_CUBIC_FEET: 1 / (1000 * _CUBIC_FOOT_TO_CUBIC_METER), } VALID_UNITS = { UnitOfVolume.LITERS, @@ -761,6 +762,7 @@ class VolumeConverter(BaseUnitConverter): UnitOfVolume.CUBIC_METERS, UnitOfVolume.CUBIC_FEET, UnitOfVolume.CENTUM_CUBIC_FEET, + UnitOfVolume.MILLE_CUBIC_FEET, } diff --git a/homeassistant/util/unit_system.py b/homeassistant/util/unit_system.py index 31f74377a16..934cd6d4b69 100644 --- a/homeassistant/util/unit_system.py +++ b/homeassistant/util/unit_system.py @@ -281,6 +281,7 @@ METRIC_SYSTEM = UnitSystem( # Convert non-metric volumes of gas meters ("gas", UnitOfVolume.CENTUM_CUBIC_FEET): UnitOfVolume.CUBIC_METERS, ("gas", UnitOfVolume.CUBIC_FEET): UnitOfVolume.CUBIC_METERS, + ("gas", UnitOfVolume.MILLE_CUBIC_FEET): UnitOfVolume.CUBIC_METERS, # Convert non-metric precipitation ("precipitation", UnitOfLength.INCHES): UnitOfLength.MILLIMETERS, # Convert non-metric precipitation intensity @@ -312,10 +313,12 @@ METRIC_SYSTEM = UnitSystem( ("volume", UnitOfVolume.CUBIC_FEET): UnitOfVolume.CUBIC_METERS, ("volume", UnitOfVolume.FLUID_OUNCES): UnitOfVolume.MILLILITERS, ("volume", UnitOfVolume.GALLONS): UnitOfVolume.LITERS, + ("volume", UnitOfVolume.MILLE_CUBIC_FEET): UnitOfVolume.CUBIC_METERS, # Convert non-metric volumes of water meters ("water", UnitOfVolume.CENTUM_CUBIC_FEET): UnitOfVolume.CUBIC_METERS, ("water", UnitOfVolume.CUBIC_FEET): UnitOfVolume.CUBIC_METERS, ("water", UnitOfVolume.GALLONS): UnitOfVolume.LITERS, + ("water", UnitOfVolume.MILLE_CUBIC_FEET): UnitOfVolume.CUBIC_METERS, # Convert wind speeds except knots to km/h **{ ("wind_speed", unit): UnitOfSpeed.KILOMETERS_PER_HOUR diff --git a/tests/components/energy/test_validate.py b/tests/components/energy/test_validate.py index 9e7a2151b04..9addf6c1001 100644 --- a/tests/components/energy/test_validate.py +++ b/tests/components/energy/test_validate.py @@ -850,7 +850,7 @@ async def test_validation_gas( "affected_entities": {("sensor.gas_consumption_1", "beers")}, "translation_placeholders": { "energy_units": ENERGY_UNITS_STRING, - "gas_units": "CCF, ft³, m³, L", + "gas_units": "CCF, ft³, m³, L, MCF", }, }, { @@ -879,7 +879,7 @@ async def test_validation_gas( "affected_entities": {("sensor.gas_price_2", "EUR/invalid")}, "translation_placeholders": { "price_units": ( - f"{ENERGY_PRICE_UNITS_STRING}, EUR/CCF, EUR/ft³, EUR/m³, EUR/L" + f"{ENERGY_PRICE_UNITS_STRING}, EUR/CCF, EUR/ft³, EUR/m³, EUR/L, EUR/MCF" ) }, }, @@ -1060,7 +1060,9 @@ async def test_validation_water( { "type": "entity_unexpected_unit_water", "affected_entities": {("sensor.water_consumption_1", "beers")}, - "translation_placeholders": {"water_units": "CCF, ft³, m³, gal, L"}, + "translation_placeholders": { + "water_units": "CCF, ft³, m³, gal, L, MCF" + }, }, { "type": "recorder_untracked", @@ -1087,7 +1089,7 @@ async def test_validation_water( "type": "entity_unexpected_unit_water_price", "affected_entities": {("sensor.water_price_2", "EUR/invalid")}, "translation_placeholders": { - "price_units": "EUR/CCF, EUR/ft³, EUR/m³, EUR/gal, EUR/L" + "price_units": "EUR/CCF, EUR/ft³, EUR/m³, EUR/gal, EUR/L, EUR/MCF" }, }, ], diff --git a/tests/components/kitchen_sink/snapshots/test_init.ambr b/tests/components/kitchen_sink/snapshots/test_init.ambr index fe22f19fb7a..c7161a3d284 100644 --- a/tests/components/kitchen_sink/snapshots/test_init.ambr +++ b/tests/components/kitchen_sink/snapshots/test_init.ambr @@ -7,7 +7,7 @@ 'metadata_unit': 'm³', 'state_unit': 'W', 'statistic_id': 'sensor.statistics_issues_issue_1', - 'supported_unit': 'CCF, L, fl. oz., ft³, gal, mL, m³', + 'supported_unit': 'CCF, L, MCF, fl. oz., ft³, gal, mL, m³', }), 'type': 'units_changed', }), @@ -35,7 +35,7 @@ 'metadata_unit': 'm³', 'state_unit': 'W', 'statistic_id': 'sensor.statistics_issues_issue_3', - 'supported_unit': 'CCF, L, fl. oz., ft³, gal, mL, m³', + 'supported_unit': 'CCF, L, MCF, fl. oz., ft³, gal, mL, m³', }), 'type': 'units_changed', }), diff --git a/tests/components/sensor/test_recorder.py b/tests/components/sensor/test_recorder.py index 8b6d55cb9a9..09f2480891e 100644 --- a/tests/components/sensor/test_recorder.py +++ b/tests/components/sensor/test_recorder.py @@ -5824,7 +5824,7 @@ async def test_validate_statistics_unit_change_equivalent_units( @pytest.mark.parametrize( ("attributes", "unit1", "unit2", "supported_unit"), [ - (NONE_SENSOR_ATTRIBUTES, "m³", "m3", "CCF, L, fl. oz., ft³, gal, mL, m³"), + (NONE_SENSOR_ATTRIBUTES, "m³", "m3", "CCF, L, MCF, fl. oz., ft³, gal, mL, m³"), (NONE_SENSOR_ATTRIBUTES, "\u03bcV", "\u00b5V", "MV, V, kV, mV, \u03bcV"), (NONE_SENSOR_ATTRIBUTES, "\u03bcS/cm", "\u00b5S/cm", "S/cm, mS/cm, \u03bcS/cm"), ( diff --git a/tests/util/test_unit_conversion.py b/tests/util/test_unit_conversion.py index 476cb667d90..3fe0078aabf 100644 --- a/tests/util/test_unit_conversion.py +++ b/tests/util/test_unit_conversion.py @@ -879,6 +879,11 @@ _CONVERTED_VALUE: dict[ (5, UnitOfVolume.CENTUM_CUBIC_FEET, 478753.24, UnitOfVolume.FLUID_OUNCES), (5, UnitOfVolume.CENTUM_CUBIC_FEET, 3740.26, UnitOfVolume.GALLONS), (5, UnitOfVolume.CENTUM_CUBIC_FEET, 14158.42, UnitOfVolume.LITERS), + (5, UnitOfVolume.MILLE_CUBIC_FEET, 5000, UnitOfVolume.CUBIC_FEET), + (5, UnitOfVolume.MILLE_CUBIC_FEET, 141.5842, UnitOfVolume.CUBIC_METERS), + (5, UnitOfVolume.MILLE_CUBIC_FEET, 4787532.4, UnitOfVolume.FLUID_OUNCES), + (5, UnitOfVolume.MILLE_CUBIC_FEET, 37402.6, UnitOfVolume.GALLONS), + (5, UnitOfVolume.MILLE_CUBIC_FEET, 141584.2, UnitOfVolume.LITERS), ], VolumeFlowRateConverter: [ ( diff --git a/tests/util/test_unit_system.py b/tests/util/test_unit_system.py index 87a9729700e..e8da55358a3 100644 --- a/tests/util/test_unit_system.py +++ b/tests/util/test_unit_system.py @@ -433,6 +433,11 @@ def test_get_unit_system_invalid(key: str) -> None: UnitOfVolume.CENTUM_CUBIC_FEET, UnitOfVolume.CUBIC_METERS, ), + ( + SensorDeviceClass.GAS, + UnitOfVolume.MILLE_CUBIC_FEET, + UnitOfVolume.CUBIC_METERS, + ), (SensorDeviceClass.GAS, UnitOfVolume.CUBIC_FEET, UnitOfVolume.CUBIC_METERS), (SensorDeviceClass.GAS, UnitOfVolume.LITERS, None), (SensorDeviceClass.GAS, UnitOfVolume.CUBIC_METERS, None), @@ -510,6 +515,11 @@ def test_get_unit_system_invalid(key: str) -> None: UnitOfVolume.CENTUM_CUBIC_FEET, UnitOfVolume.CUBIC_METERS, ), + ( + SensorDeviceClass.VOLUME, + UnitOfVolume.MILLE_CUBIC_FEET, + UnitOfVolume.CUBIC_METERS, + ), (SensorDeviceClass.VOLUME, UnitOfVolume.CUBIC_FEET, UnitOfVolume.CUBIC_METERS), (SensorDeviceClass.VOLUME, UnitOfVolume.FLUID_OUNCES, UnitOfVolume.MILLILITERS), (SensorDeviceClass.VOLUME, UnitOfVolume.GALLONS, UnitOfVolume.LITERS), @@ -523,6 +533,11 @@ def test_get_unit_system_invalid(key: str) -> None: UnitOfVolume.CENTUM_CUBIC_FEET, UnitOfVolume.CUBIC_METERS, ), + ( + SensorDeviceClass.WATER, + UnitOfVolume.MILLE_CUBIC_FEET, + UnitOfVolume.CUBIC_METERS, + ), (SensorDeviceClass.WATER, UnitOfVolume.CUBIC_FEET, UnitOfVolume.CUBIC_METERS), (SensorDeviceClass.WATER, UnitOfVolume.GALLONS, UnitOfVolume.LITERS), (SensorDeviceClass.WATER, UnitOfVolume.CUBIC_METERS, None), @@ -690,6 +705,7 @@ def test_metric_converted_units(device_class: SensorDeviceClass) -> None: (SensorDeviceClass.DISTANCE, "very_long", None), # Test gas meter conversion (SensorDeviceClass.GAS, UnitOfVolume.CENTUM_CUBIC_FEET, None), + (SensorDeviceClass.GAS, UnitOfVolume.MILLE_CUBIC_FEET, None), (SensorDeviceClass.GAS, UnitOfVolume.CUBIC_METERS, UnitOfVolume.CUBIC_FEET), (SensorDeviceClass.GAS, UnitOfVolume.LITERS, UnitOfVolume.CUBIC_FEET), (SensorDeviceClass.GAS, UnitOfVolume.CUBIC_FEET, None), @@ -770,6 +786,7 @@ def test_metric_converted_units(device_class: SensorDeviceClass) -> None: (SensorDeviceClass.VOLUME, UnitOfVolume.LITERS, UnitOfVolume.GALLONS), (SensorDeviceClass.VOLUME, UnitOfVolume.MILLILITERS, UnitOfVolume.FLUID_OUNCES), (SensorDeviceClass.VOLUME, UnitOfVolume.CENTUM_CUBIC_FEET, None), + (SensorDeviceClass.VOLUME, UnitOfVolume.MILLE_CUBIC_FEET, None), (SensorDeviceClass.VOLUME, UnitOfVolume.CUBIC_FEET, None), (SensorDeviceClass.VOLUME, UnitOfVolume.FLUID_OUNCES, None), (SensorDeviceClass.VOLUME, UnitOfVolume.GALLONS, None), @@ -778,6 +795,7 @@ def test_metric_converted_units(device_class: SensorDeviceClass) -> None: (SensorDeviceClass.WATER, UnitOfVolume.CUBIC_METERS, UnitOfVolume.CUBIC_FEET), (SensorDeviceClass.WATER, UnitOfVolume.LITERS, UnitOfVolume.GALLONS), (SensorDeviceClass.WATER, UnitOfVolume.CENTUM_CUBIC_FEET, None), + (SensorDeviceClass.WATER, UnitOfVolume.MILLE_CUBIC_FEET, None), (SensorDeviceClass.WATER, UnitOfVolume.CUBIC_FEET, None), (SensorDeviceClass.WATER, UnitOfVolume.GALLONS, None), (SensorDeviceClass.WATER, "very_much", None), @@ -828,7 +846,11 @@ UNCONVERTED_UNITS_US_SYSTEM = { UnitOfLength.MILES, UnitOfLength.YARDS, ), - SensorDeviceClass.GAS: (UnitOfVolume.CENTUM_CUBIC_FEET, UnitOfVolume.CUBIC_FEET), + SensorDeviceClass.GAS: ( + UnitOfVolume.CENTUM_CUBIC_FEET, + UnitOfVolume.MILLE_CUBIC_FEET, + UnitOfVolume.CUBIC_FEET, + ), SensorDeviceClass.PRECIPITATION: (UnitOfLength.INCHES,), SensorDeviceClass.PRECIPITATION_INTENSITY: ( UnitOfVolumetricFlux.INCHES_PER_DAY, @@ -846,12 +868,14 @@ UNCONVERTED_UNITS_US_SYSTEM = { ), SensorDeviceClass.VOLUME: ( UnitOfVolume.CENTUM_CUBIC_FEET, + UnitOfVolume.MILLE_CUBIC_FEET, UnitOfVolume.CUBIC_FEET, UnitOfVolume.FLUID_OUNCES, UnitOfVolume.GALLONS, ), SensorDeviceClass.WATER: ( UnitOfVolume.CENTUM_CUBIC_FEET, + UnitOfVolume.MILLE_CUBIC_FEET, UnitOfVolume.CUBIC_FEET, UnitOfVolume.GALLONS, ),