diff --git a/homeassistant/components/dsmr/const.py b/homeassistant/components/dsmr/const.py index a5e51816183..b5fb74bbbe6 100644 --- a/homeassistant/components/dsmr/const.py +++ b/homeassistant/components/dsmr/const.py @@ -9,6 +9,7 @@ from homeassistant.components.sensor import STATE_CLASS_MEASUREMENT from homeassistant.const import ( DEVICE_CLASS_CURRENT, DEVICE_CLASS_ENERGY, + DEVICE_CLASS_GAS, DEVICE_CLASS_POWER, DEVICE_CLASS_VOLTAGE, ) @@ -256,6 +257,7 @@ SENSORS: tuple[DSMRSensorEntityDescription, ...] = ( is_gas=True, force_update=True, icon="mdi:fire", + device_class=DEVICE_CLASS_GAS, last_reset=dt.utc_from_timestamp(0), state_class=STATE_CLASS_MEASUREMENT, ), @@ -266,6 +268,7 @@ SENSORS: tuple[DSMRSensorEntityDescription, ...] = ( is_gas=True, force_update=True, icon="mdi:fire", + device_class=DEVICE_CLASS_GAS, last_reset=dt.utc_from_timestamp(0), state_class=STATE_CLASS_MEASUREMENT, ), @@ -276,6 +279,7 @@ SENSORS: tuple[DSMRSensorEntityDescription, ...] = ( is_gas=True, force_update=True, icon="mdi:fire", + device_class=DEVICE_CLASS_GAS, last_reset=dt.utc_from_timestamp(0), state_class=STATE_CLASS_MEASUREMENT, ), diff --git a/homeassistant/components/dsmr/sensor.py b/homeassistant/components/dsmr/sensor.py index ae3fb6b01a4..dbc29144719 100644 --- a/homeassistant/components/dsmr/sensor.py +++ b/homeassistant/components/dsmr/sensor.py @@ -16,7 +16,12 @@ import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry -from homeassistant.const import CONF_HOST, CONF_PORT, EVENT_HOMEASSISTANT_STOP +from homeassistant.const import ( + CONF_HOST, + CONF_PORT, + EVENT_HOMEASSISTANT_STOP, + VOLUME_CUBIC_METERS, +) from homeassistant.core import CoreState, HomeAssistant, callback from homeassistant.helpers import config_validation as cv from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -56,6 +61,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( } ) +UNIT_CONVERSION = {"m3": VOLUME_CUBIC_METERS} + async def async_setup_platform( hass: HomeAssistant, @@ -260,7 +267,10 @@ class DSMREntity(SensorEntity): @property def native_unit_of_measurement(self) -> str | None: """Return the unit of measurement of this entity, if any.""" - return self.get_dsmr_object_attr("unit") + unit_of_measurement = self.get_dsmr_object_attr("unit") + if unit_of_measurement in UNIT_CONVERSION: + return UNIT_CONVERSION[unit_of_measurement] + return unit_of_measurement @staticmethod def translate_tariff(value: str, dsmr_version: str) -> str | None: diff --git a/homeassistant/components/recorder/statistics.py b/homeassistant/components/recorder/statistics.py index f3b0b27df39..b91e4d160df 100644 --- a/homeassistant/components/recorder/statistics.py +++ b/homeassistant/components/recorder/statistics.py @@ -11,13 +11,19 @@ from sqlalchemy import bindparam from sqlalchemy.ext import baked from sqlalchemy.orm.scoping import scoped_session -from homeassistant.const import PRESSURE_PA, TEMP_CELSIUS +from homeassistant.const import ( + PRESSURE_PA, + TEMP_CELSIUS, + VOLUME_CUBIC_FEET, + VOLUME_CUBIC_METERS, +) from homeassistant.core import Event, HomeAssistant, callback from homeassistant.helpers import entity_registry import homeassistant.util.dt as dt_util import homeassistant.util.pressure as pressure_util import homeassistant.util.temperature as temperature_util from homeassistant.util.unit_system import UnitSystem +import homeassistant.util.volume as volume_util from .const import DOMAIN from .models import ( @@ -64,6 +70,11 @@ UNIT_CONVERSIONS = { ) if x is not None else None, + VOLUME_CUBIC_METERS: lambda x, units: volume_util.convert( + x, VOLUME_CUBIC_METERS, _configured_unit(VOLUME_CUBIC_METERS, units) + ) + if x is not None + else None, } _LOGGER = logging.getLogger(__name__) @@ -214,6 +225,10 @@ def _configured_unit(unit: str, units: UnitSystem) -> str: return units.pressure_unit if unit == TEMP_CELSIUS: return units.temperature_unit + if unit == VOLUME_CUBIC_METERS: + if units.is_metric: + return VOLUME_CUBIC_METERS + return VOLUME_CUBIC_FEET return unit diff --git a/homeassistant/components/sensor/__init__.py b/homeassistant/components/sensor/__init__.py index c88b7da13f4..483d8b88f2e 100644 --- a/homeassistant/components/sensor/__init__.py +++ b/homeassistant/components/sensor/__init__.py @@ -17,6 +17,7 @@ from homeassistant.const import ( DEVICE_CLASS_CO2, DEVICE_CLASS_CURRENT, DEVICE_CLASS_ENERGY, + DEVICE_CLASS_GAS, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_MONETARY, @@ -65,6 +66,7 @@ DEVICE_CLASSES: Final[list[str]] = [ DEVICE_CLASS_POWER, # power (W/kW) DEVICE_CLASS_POWER_FACTOR, # power factor (%) DEVICE_CLASS_VOLTAGE, # voltage (V) + DEVICE_CLASS_GAS, # gas (m³ or ft³) ] DEVICE_CLASSES_SCHEMA: Final = vol.All(vol.Lower, vol.In(DEVICE_CLASSES)) diff --git a/homeassistant/components/sensor/device_condition.py b/homeassistant/components/sensor/device_condition.py index a77ed2d2cd7..3b9f3839cfb 100644 --- a/homeassistant/components/sensor/device_condition.py +++ b/homeassistant/components/sensor/device_condition.py @@ -16,6 +16,7 @@ from homeassistant.const import ( DEVICE_CLASS_CO2, DEVICE_CLASS_CURRENT, DEVICE_CLASS_ENERGY, + DEVICE_CLASS_GAS, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_POWER, @@ -46,6 +47,7 @@ CONF_IS_CO2 = "is_carbon_dioxide" CONF_IS_CURRENT = "is_current" CONF_IS_ENERGY = "is_energy" CONF_IS_HUMIDITY = "is_humidity" +CONF_IS_GAS = "is_gas" CONF_IS_ILLUMINANCE = "is_illuminance" CONF_IS_POWER = "is_power" CONF_IS_POWER_FACTOR = "is_power_factor" @@ -61,6 +63,7 @@ ENTITY_CONDITIONS = { DEVICE_CLASS_CO2: [{CONF_TYPE: CONF_IS_CO2}], DEVICE_CLASS_CURRENT: [{CONF_TYPE: CONF_IS_CURRENT}], DEVICE_CLASS_ENERGY: [{CONF_TYPE: CONF_IS_ENERGY}], + DEVICE_CLASS_GAS: [{CONF_TYPE: CONF_IS_GAS}], DEVICE_CLASS_HUMIDITY: [{CONF_TYPE: CONF_IS_HUMIDITY}], DEVICE_CLASS_ILLUMINANCE: [{CONF_TYPE: CONF_IS_ILLUMINANCE}], DEVICE_CLASS_POWER: [{CONF_TYPE: CONF_IS_POWER}], @@ -83,6 +86,7 @@ CONDITION_SCHEMA = vol.All( CONF_IS_CO2, CONF_IS_CURRENT, CONF_IS_ENERGY, + CONF_IS_GAS, CONF_IS_HUMIDITY, CONF_IS_ILLUMINANCE, CONF_IS_POWER, diff --git a/homeassistant/components/sensor/device_trigger.py b/homeassistant/components/sensor/device_trigger.py index 3b00bae816d..f7d72dd4c1b 100644 --- a/homeassistant/components/sensor/device_trigger.py +++ b/homeassistant/components/sensor/device_trigger.py @@ -19,6 +19,7 @@ from homeassistant.const import ( DEVICE_CLASS_CO2, DEVICE_CLASS_CURRENT, DEVICE_CLASS_ENERGY, + DEVICE_CLASS_GAS, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_POWER, @@ -44,6 +45,7 @@ CONF_CO = "carbon_monoxide" CONF_CO2 = "carbon_dioxide" CONF_CURRENT = "current" CONF_ENERGY = "energy" +CONF_GAS = "gas" CONF_HUMIDITY = "humidity" CONF_ILLUMINANCE = "illuminance" CONF_POWER = "power" @@ -60,6 +62,7 @@ ENTITY_TRIGGERS = { DEVICE_CLASS_CO2: [{CONF_TYPE: CONF_CO2}], DEVICE_CLASS_CURRENT: [{CONF_TYPE: CONF_CURRENT}], DEVICE_CLASS_ENERGY: [{CONF_TYPE: CONF_ENERGY}], + DEVICE_CLASS_GAS: [{CONF_TYPE: CONF_GAS}], DEVICE_CLASS_HUMIDITY: [{CONF_TYPE: CONF_HUMIDITY}], DEVICE_CLASS_ILLUMINANCE: [{CONF_TYPE: CONF_ILLUMINANCE}], DEVICE_CLASS_POWER: [{CONF_TYPE: CONF_POWER}], @@ -83,6 +86,7 @@ TRIGGER_SCHEMA = vol.All( CONF_CO2, CONF_CURRENT, CONF_ENERGY, + CONF_GAS, CONF_HUMIDITY, CONF_ILLUMINANCE, CONF_POWER, diff --git a/homeassistant/components/sensor/recorder.py b/homeassistant/components/sensor/recorder.py index afcfe2f228d..fb7393cfe1d 100644 --- a/homeassistant/components/sensor/recorder.py +++ b/homeassistant/components/sensor/recorder.py @@ -11,6 +11,7 @@ from homeassistant.components.sensor import ( ATTR_STATE_CLASS, DEVICE_CLASS_BATTERY, DEVICE_CLASS_ENERGY, + DEVICE_CLASS_GAS, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_MONETARY, DEVICE_CLASS_PRESSURE, @@ -35,11 +36,14 @@ from homeassistant.const import ( TEMP_CELSIUS, TEMP_FAHRENHEIT, TEMP_KELVIN, + VOLUME_CUBIC_FEET, + VOLUME_CUBIC_METERS, ) from homeassistant.core import HomeAssistant, State import homeassistant.util.dt as dt_util import homeassistant.util.pressure as pressure_util import homeassistant.util.temperature as temperature_util +import homeassistant.util.volume as volume_util from . import ATTR_LAST_RESET, DOMAIN @@ -53,6 +57,7 @@ DEVICE_CLASS_OR_UNIT_STATISTICS = { DEVICE_CLASS_POWER: {"mean", "min", "max"}, DEVICE_CLASS_PRESSURE: {"mean", "min", "max"}, DEVICE_CLASS_TEMPERATURE: {"mean", "min", "max"}, + DEVICE_CLASS_GAS: {"sum"}, PERCENTAGE: {"mean", "min", "max"}, } @@ -62,6 +67,7 @@ DEVICE_CLASS_UNITS = { DEVICE_CLASS_POWER: POWER_WATT, DEVICE_CLASS_PRESSURE: PRESSURE_PA, DEVICE_CLASS_TEMPERATURE: TEMP_CELSIUS, + DEVICE_CLASS_GAS: VOLUME_CUBIC_METERS, } UNIT_CONVERSIONS: dict[str, dict[str, Callable]] = { @@ -92,6 +98,11 @@ UNIT_CONVERSIONS: dict[str, dict[str, Callable]] = { TEMP_FAHRENHEIT: temperature_util.fahrenheit_to_celsius, TEMP_KELVIN: temperature_util.kelvin_to_celsius, }, + # Convert volume to cubic meter + DEVICE_CLASS_GAS: { + VOLUME_CUBIC_METERS: lambda x: x, + VOLUME_CUBIC_FEET: volume_util.cubic_feet_to_cubic_meter, + }, } # Keep track of entities for which a warning about unsupported unit has been logged diff --git a/homeassistant/components/sensor/strings.json b/homeassistant/components/sensor/strings.json index efe5366cfec..54d0f9ad76c 100644 --- a/homeassistant/components/sensor/strings.json +++ b/homeassistant/components/sensor/strings.json @@ -5,6 +5,7 @@ "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_gas": "Current {entity_name} gas", "is_humidity": "Current {entity_name} humidity", "is_illuminance": "Current {entity_name} illuminance", "is_power": "Current {entity_name} power", @@ -21,6 +22,7 @@ "battery_level": "{entity_name} battery level changes", "carbon_monoxide": "{entity_name} carbon monoxide concentration changes", "carbon_dioxide": "{entity_name} carbon dioxide concentration changes", + "gas": "{entity_name} gas changes", "humidity": "{entity_name} humidity changes", "illuminance": "{entity_name} illuminance changes", "power": "{entity_name} power changes", diff --git a/homeassistant/components/toon/const.py b/homeassistant/components/toon/const.py index 4af57e03412..1c9192c4544 100644 --- a/homeassistant/components/toon/const.py +++ b/homeassistant/components/toon/const.py @@ -18,10 +18,12 @@ from homeassistant.const import ( ATTR_ICON, ATTR_NAME, ATTR_UNIT_OF_MEASUREMENT, + DEVICE_CLASS_GAS, ENERGY_KILO_WATT_HOUR, PERCENTAGE, POWER_WATT, TEMP_CELSIUS, + VOLUME_CUBIC_METERS, ) from homeassistant.util import dt as dt_util @@ -38,7 +40,6 @@ DEFAULT_MIN_TEMP = 6.0 CURRENCY_EUR = "EUR" VOLUME_CM3 = "CM3" -VOLUME_M3 = "M3" VOLUME_LHOUR = "L/H" VOLUME_LMIN = "L/MIN" @@ -125,7 +126,8 @@ SENSOR_ENTITIES = { ATTR_NAME: "Average Daily Gas Usage", ATTR_SECTION: "gas_usage", ATTR_MEASUREMENT: "day_average", - ATTR_UNIT_OF_MEASUREMENT: VOLUME_M3, + ATTR_DEVICE_CLASS: DEVICE_CLASS_GAS, + ATTR_UNIT_OF_MEASUREMENT: VOLUME_CUBIC_METERS, ATTR_ICON: "mdi:gas-cylinder", ATTR_DEFAULT_ENABLED: False, }, @@ -133,7 +135,8 @@ SENSOR_ENTITIES = { ATTR_NAME: "Gas Usage Today", ATTR_SECTION: "gas_usage", ATTR_MEASUREMENT: "day_usage", - ATTR_UNIT_OF_MEASUREMENT: VOLUME_M3, + ATTR_DEVICE_CLASS: DEVICE_CLASS_GAS, + ATTR_UNIT_OF_MEASUREMENT: VOLUME_CUBIC_METERS, ATTR_ICON: "mdi:gas-cylinder", }, "gas_daily_cost": { @@ -147,9 +150,10 @@ SENSOR_ENTITIES = { ATTR_NAME: "Gas Meter", ATTR_SECTION: "gas_usage", ATTR_MEASUREMENT: "meter", - ATTR_UNIT_OF_MEASUREMENT: VOLUME_M3, + ATTR_UNIT_OF_MEASUREMENT: VOLUME_CUBIC_METERS, ATTR_ICON: "mdi:gas-cylinder", ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT, + ATTR_DEVICE_CLASS: DEVICE_CLASS_GAS, ATTR_LAST_RESET: dt_util.utc_from_timestamp(0), ATTR_DEFAULT_ENABLED: False, }, @@ -321,7 +325,7 @@ SENSOR_ENTITIES = { ATTR_NAME: "Average Daily Water Usage", ATTR_SECTION: "water_usage", ATTR_MEASUREMENT: "day_average", - ATTR_UNIT_OF_MEASUREMENT: VOLUME_M3, + ATTR_UNIT_OF_MEASUREMENT: VOLUME_CUBIC_METERS, ATTR_ICON: "mdi:water", ATTR_DEFAULT_ENABLED: False, }, @@ -329,7 +333,7 @@ SENSOR_ENTITIES = { ATTR_NAME: "Water Usage Today", ATTR_SECTION: "water_usage", ATTR_MEASUREMENT: "day_usage", - ATTR_UNIT_OF_MEASUREMENT: VOLUME_M3, + ATTR_UNIT_OF_MEASUREMENT: VOLUME_CUBIC_METERS, ATTR_ICON: "mdi:water", ATTR_DEFAULT_ENABLED: False, }, @@ -337,7 +341,7 @@ SENSOR_ENTITIES = { ATTR_NAME: "Water Meter", ATTR_SECTION: "water_usage", ATTR_MEASUREMENT: "meter", - ATTR_UNIT_OF_MEASUREMENT: VOLUME_M3, + ATTR_UNIT_OF_MEASUREMENT: VOLUME_CUBIC_METERS, ATTR_ICON: "mdi:water", ATTR_DEFAULT_ENABLED: False, ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT, diff --git a/homeassistant/const.py b/homeassistant/const.py index 5f4f8cd084c..9fa5c2cd231 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -247,6 +247,7 @@ DEVICE_CLASS_SIGNAL_STRENGTH: Final = "signal_strength" DEVICE_CLASS_TEMPERATURE: Final = "temperature" DEVICE_CLASS_TIMESTAMP: Final = "timestamp" DEVICE_CLASS_VOLTAGE: Final = "voltage" +DEVICE_CLASS_GAS: Final = "gas" # #### STATES #### STATE_ON: Final = "on" diff --git a/homeassistant/util/volume.py b/homeassistant/util/volume.py index f4a02dbe82e..84a3faa0951 100644 --- a/homeassistant/util/volume.py +++ b/homeassistant/util/volume.py @@ -6,6 +6,8 @@ from numbers import Number from homeassistant.const import ( UNIT_NOT_RECOGNIZED_TEMPLATE, VOLUME, + VOLUME_CUBIC_FEET, + VOLUME_CUBIC_METERS, VOLUME_FLUID_OUNCE, VOLUME_GALLONS, VOLUME_LITERS, @@ -17,19 +19,31 @@ VALID_UNITS: tuple[str, ...] = ( VOLUME_MILLILITERS, VOLUME_GALLONS, VOLUME_FLUID_OUNCE, + VOLUME_CUBIC_METERS, + VOLUME_CUBIC_FEET, ) -def __liter_to_gallon(liter: float) -> float: +def liter_to_gallon(liter: float) -> float: """Convert a volume measurement in Liter to Gallon.""" return liter * 0.2642 -def __gallon_to_liter(gallon: float) -> float: +def gallon_to_liter(gallon: float) -> float: """Convert a volume measurement in Gallon to Liter.""" return gallon * 3.785 +def cubic_meter_to_cubic_feet(cubic_meter: float) -> float: + """Convert a volume measurement in cubic meter to cubic feet.""" + return cubic_meter * 35.3146667 + + +def cubic_feet_to_cubic_meter(cubic_feet: float) -> float: + """Convert a volume measurement in cubic feet to cubic meter.""" + return cubic_feet * 0.0283168466 + + def convert(volume: float, from_unit: str, to_unit: str) -> float: """Convert a temperature from one unit to another.""" if from_unit not in VALID_UNITS: @@ -45,8 +59,12 @@ def convert(volume: float, from_unit: str, to_unit: str) -> float: result: float = volume if from_unit == VOLUME_LITERS and to_unit == VOLUME_GALLONS: - result = __liter_to_gallon(volume) + result = liter_to_gallon(volume) elif from_unit == VOLUME_GALLONS and to_unit == VOLUME_LITERS: - result = __gallon_to_liter(volume) + result = gallon_to_liter(volume) + elif from_unit == VOLUME_CUBIC_METERS and to_unit == VOLUME_CUBIC_FEET: + result = cubic_meter_to_cubic_feet(volume) + elif from_unit == VOLUME_CUBIC_FEET and to_unit == VOLUME_CUBIC_METERS: + result = cubic_feet_to_cubic_meter(volume) return result diff --git a/tests/components/dsmr/test_sensor.py b/tests/components/dsmr/test_sensor.py index 90194eaeb6b..c7e0addd800 100644 --- a/tests/components/dsmr/test_sensor.py +++ b/tests/components/dsmr/test_sensor.py @@ -24,6 +24,7 @@ from homeassistant.const import ( ATTR_ICON, ATTR_UNIT_OF_MEASUREMENT, DEVICE_CLASS_ENERGY, + DEVICE_CLASS_GAS, DEVICE_CLASS_POWER, ENERGY_KILO_WATT_HOUR, STATE_UNKNOWN, @@ -104,7 +105,7 @@ async def test_default_setup(hass, dsmr_connection_fixture): GAS_METER_READING: MBusObject( [ {"value": datetime.datetime.fromtimestamp(1551642213)}, - {"value": Decimal(745.695), "unit": VOLUME_CUBIC_METERS}, + {"value": Decimal(745.695), "unit": "m3"}, ] ), } @@ -164,7 +165,7 @@ async def test_default_setup(hass, dsmr_connection_fixture): # check if gas consumption is parsed correctly gas_consumption = hass.states.get("sensor.gas_consumption") assert gas_consumption.state == "745.695" - assert gas_consumption.attributes.get(ATTR_DEVICE_CLASS) is None + assert gas_consumption.attributes.get(ATTR_DEVICE_CLASS) == DEVICE_CLASS_GAS assert gas_consumption.attributes.get(ATTR_ICON) == "mdi:fire" assert gas_consumption.attributes.get(ATTR_LAST_RESET) is not None assert gas_consumption.attributes.get(ATTR_STATE_CLASS) == STATE_CLASS_MEASUREMENT @@ -228,7 +229,7 @@ async def test_v4_meter(hass, dsmr_connection_fixture): HOURLY_GAS_METER_READING: MBusObject( [ {"value": datetime.datetime.fromtimestamp(1551642213)}, - {"value": Decimal(745.695), "unit": VOLUME_CUBIC_METERS}, + {"value": Decimal(745.695), "unit": "m3"}, ] ), ELECTRICITY_ACTIVE_TARIFF: CosemObject([{"value": "0001", "unit": ""}]), @@ -263,8 +264,8 @@ async def test_v4_meter(hass, dsmr_connection_fixture): # check if gas consumption is parsed correctly gas_consumption = hass.states.get("sensor.gas_consumption") assert gas_consumption.state == "745.695" + assert gas_consumption.attributes.get(ATTR_DEVICE_CLASS) == DEVICE_CLASS_GAS assert gas_consumption.attributes.get("unit_of_measurement") == VOLUME_CUBIC_METERS - assert gas_consumption.attributes.get(ATTR_DEVICE_CLASS) is None assert gas_consumption.attributes.get(ATTR_ICON) == "mdi:fire" assert gas_consumption.attributes.get(ATTR_LAST_RESET) is not None assert gas_consumption.attributes.get(ATTR_STATE_CLASS) == STATE_CLASS_MEASUREMENT @@ -299,7 +300,7 @@ async def test_v5_meter(hass, dsmr_connection_fixture): HOURLY_GAS_METER_READING: MBusObject( [ {"value": datetime.datetime.fromtimestamp(1551642213)}, - {"value": Decimal(745.695), "unit": VOLUME_CUBIC_METERS}, + {"value": Decimal(745.695), "unit": "m3"}, ] ), ELECTRICITY_ACTIVE_TARIFF: CosemObject([{"value": "0001", "unit": ""}]), @@ -334,7 +335,7 @@ async def test_v5_meter(hass, dsmr_connection_fixture): # check if gas consumption is parsed correctly gas_consumption = hass.states.get("sensor.gas_consumption") assert gas_consumption.state == "745.695" - assert gas_consumption.attributes.get(ATTR_DEVICE_CLASS) is None + assert gas_consumption.attributes.get(ATTR_DEVICE_CLASS) == DEVICE_CLASS_GAS assert gas_consumption.attributes.get(ATTR_ICON) == "mdi:fire" assert gas_consumption.attributes.get(ATTR_LAST_RESET) is not None assert gas_consumption.attributes.get(ATTR_STATE_CLASS) == STATE_CLASS_MEASUREMENT @@ -370,7 +371,7 @@ async def test_luxembourg_meter(hass, dsmr_connection_fixture): HOURLY_GAS_METER_READING: MBusObject( [ {"value": datetime.datetime.fromtimestamp(1551642213)}, - {"value": Decimal(745.695), "unit": VOLUME_CUBIC_METERS}, + {"value": Decimal(745.695), "unit": "m3"}, ] ), LUXEMBOURG_ELECTRICITY_USED_TARIFF_GLOBAL: CosemObject( @@ -415,7 +416,7 @@ async def test_luxembourg_meter(hass, dsmr_connection_fixture): # check if gas consumption is parsed correctly gas_consumption = hass.states.get("sensor.gas_consumption") assert gas_consumption.state == "745.695" - assert gas_consumption.attributes.get(ATTR_DEVICE_CLASS) is None + assert gas_consumption.attributes.get(ATTR_DEVICE_CLASS) == DEVICE_CLASS_GAS assert gas_consumption.attributes.get(ATTR_ICON) == "mdi:fire" assert gas_consumption.attributes.get(ATTR_LAST_RESET) is not None assert gas_consumption.attributes.get(ATTR_STATE_CLASS) == STATE_CLASS_MEASUREMENT @@ -450,7 +451,7 @@ async def test_belgian_meter(hass, dsmr_connection_fixture): BELGIUM_HOURLY_GAS_METER_READING: MBusObject( [ {"value": datetime.datetime.fromtimestamp(1551642213)}, - {"value": Decimal(745.695), "unit": VOLUME_CUBIC_METERS}, + {"value": Decimal(745.695), "unit": "m3"}, ] ), ELECTRICITY_ACTIVE_TARIFF: CosemObject([{"value": "0001", "unit": ""}]), @@ -485,7 +486,7 @@ async def test_belgian_meter(hass, dsmr_connection_fixture): # check if gas consumption is parsed correctly gas_consumption = hass.states.get("sensor.gas_consumption") assert gas_consumption.state == "745.695" - assert gas_consumption.attributes.get(ATTR_DEVICE_CLASS) is None + assert gas_consumption.attributes.get(ATTR_DEVICE_CLASS) is DEVICE_CLASS_GAS assert gas_consumption.attributes.get(ATTR_ICON) == "mdi:fire" assert gas_consumption.attributes.get(ATTR_LAST_RESET) is not None assert gas_consumption.attributes.get(ATTR_STATE_CLASS) == STATE_CLASS_MEASUREMENT diff --git a/tests/components/sensor/test_device_trigger.py b/tests/components/sensor/test_device_trigger.py index ce35e2506a9..f955c3c19db 100644 --- a/tests/components/sensor/test_device_trigger.py +++ b/tests/components/sensor/test_device_trigger.py @@ -86,7 +86,7 @@ async def test_get_triggers(hass, device_reg, entity_reg, enable_custom_integrat if device_class != "none" ] triggers = await async_get_device_automations(hass, "trigger", device_entry.id) - assert len(triggers) == 13 + assert len(triggers) == 14 assert triggers == expected_triggers diff --git a/tests/components/sensor/test_recorder.py b/tests/components/sensor/test_recorder.py index 58614e86a0e..a612bc75a77 100644 --- a/tests/components/sensor/test_recorder.py +++ b/tests/components/sensor/test_recorder.py @@ -39,6 +39,11 @@ TEMPERATURE_SENSOR_ATTRIBUTES = { "state_class": "measurement", "unit_of_measurement": "°C", } +GAS_SENSOR_ATTRIBUTES = { + "device_class": "gas", + "state_class": "measurement", + "unit_of_measurement": "m³", +} @pytest.mark.parametrize( @@ -154,11 +159,13 @@ def test_compile_hourly_statistics_unsupported(hass_recorder, caplog, attributes [ ("energy", "kWh", "kWh", 1), ("energy", "Wh", "kWh", 1 / 1000), - ("monetary", "€", "€", 1), + ("monetary", "EUR", "EUR", 1), ("monetary", "SEK", "SEK", 1), + ("gas", "m³", "m³", 1), + ("gas", "ft³", "m³", 0.0283168466), ], ) -def test_compile_hourly_energy_statistics( +def test_compile_hourly_sum_statistics( hass_recorder, caplog, device_class, unit, native_unit, factor ): """Test compiling hourly statistics.""" @@ -174,7 +181,7 @@ def test_compile_hourly_energy_statistics( } seq = [10, 15, 20, 10, 30, 40, 50, 60, 70] - four, eight, states = record_energy_states( + four, eight, states = record_meter_states( hass, zero, "sensor.test1", attributes, seq ) hist = history.get_significant_states( @@ -254,14 +261,14 @@ def test_compile_hourly_energy_statistics_unsupported(hass_recorder, caplog): seq3 = [0, 0, 5, 10, 30, 50, 60, 80, 90] seq4 = [0, 0, 5, 10, 30, 50, 60, 80, 90] - four, eight, states = record_energy_states( + four, eight, states = record_meter_states( hass, zero, "sensor.test1", sns1_attr, seq1 ) - _, _, _states = record_energy_states(hass, zero, "sensor.test2", sns2_attr, seq2) + _, _, _states = record_meter_states(hass, zero, "sensor.test2", sns2_attr, seq2) states = {**states, **_states} - _, _, _states = record_energy_states(hass, zero, "sensor.test3", sns3_attr, seq3) + _, _, _states = record_meter_states(hass, zero, "sensor.test3", sns3_attr, seq3) states = {**states, **_states} - _, _, _states = record_energy_states(hass, zero, "sensor.test4", sns4_attr, seq4) + _, _, _states = record_meter_states(hass, zero, "sensor.test4", sns4_attr, seq4) states = {**states, **_states} hist = history.get_significant_states( @@ -336,14 +343,14 @@ def test_compile_hourly_energy_statistics_multiple(hass_recorder, caplog): seq3 = [0, 0, 5, 10, 30, 50, 60, 80, 90] seq4 = [0, 0, 5, 10, 30, 50, 60, 80, 90] - four, eight, states = record_energy_states( + four, eight, states = record_meter_states( hass, zero, "sensor.test1", sns1_attr, seq1 ) - _, _, _states = record_energy_states(hass, zero, "sensor.test2", sns2_attr, seq2) + _, _, _states = record_meter_states(hass, zero, "sensor.test2", sns2_attr, seq2) states = {**states, **_states} - _, _, _states = record_energy_states(hass, zero, "sensor.test3", sns3_attr, seq3) + _, _, _states = record_meter_states(hass, zero, "sensor.test3", sns3_attr, seq3) states = {**states, **_states} - _, _, _states = record_energy_states(hass, zero, "sensor.test4", sns4_attr, seq4) + _, _, _states = record_meter_states(hass, zero, "sensor.test4", sns4_attr, seq4) states = {**states, **_states} hist = history.get_significant_states( hass, zero - timedelta.resolution, eight + timedelta.resolution @@ -632,6 +639,8 @@ def test_compile_hourly_statistics_fails(hass_recorder, caplog): ("humidity", None, None, "mean"), ("monetary", "USD", "USD", "sum"), ("monetary", "None", "None", "sum"), + ("gas", "m³", "m³", "sum"), + ("gas", "ft³", "m³", "sum"), ("pressure", "Pa", "Pa", "mean"), ("pressure", "hPa", "Pa", "mean"), ("pressure", "mbar", "Pa", "mean"), @@ -697,7 +706,7 @@ def test_list_statistic_ids_unsupported(hass_recorder, caplog, _attributes): def record_states(hass, zero, entity_id, attributes): """Record some test states. - We inject a bunch of state updates for temperature sensors. + We inject a bunch of state updates for measurement sensors. """ attributes = dict(attributes) @@ -725,10 +734,10 @@ def record_states(hass, zero, entity_id, attributes): return four, states -def record_energy_states(hass, zero, entity_id, _attributes, seq): +def record_meter_states(hass, zero, entity_id, _attributes, seq): """Record some test states. - We inject a bunch of state updates for energy sensors. + We inject a bunch of state updates for meter sensors. """ def set_state(entity_id, state, **kwargs): diff --git a/tests/testing_config/custom_components/test/sensor.py b/tests/testing_config/custom_components/test/sensor.py index 7c121d1c05a..f4b2e96321e 100644 --- a/tests/testing_config/custom_components/test/sensor.py +++ b/tests/testing_config/custom_components/test/sensor.py @@ -9,6 +9,7 @@ from homeassistant.const import ( PERCENTAGE, PRESSURE_HPA, SIGNAL_STRENGTH_DECIBELS, + VOLUME_CUBIC_METERS, ) from tests.common import MockEntity @@ -30,6 +31,7 @@ UNITS_OF_MEASUREMENT = { sensor.DEVICE_CLASS_ENERGY: "kWh", # energy (Wh/kWh) sensor.DEVICE_CLASS_POWER_FACTOR: PERCENTAGE, # power factor (no unit, min: -1.0, max: 1.0) sensor.DEVICE_CLASS_VOLTAGE: "V", # voltage (V) + sensor.DEVICE_CLASS_GAS: VOLUME_CUBIC_METERS, # gas (m³) } ENTITIES = {} diff --git a/tests/util/test_volume.py b/tests/util/test_volume.py index 2c596d92e5b..3cbf5b72130 100644 --- a/tests/util/test_volume.py +++ b/tests/util/test_volume.py @@ -3,6 +3,8 @@ import pytest from homeassistant.const import ( + VOLUME_CUBIC_FEET, + VOLUME_CUBIC_METERS, VOLUME_FLUID_OUNCE, VOLUME_GALLONS, VOLUME_LITERS, @@ -47,3 +49,21 @@ def test_convert_from_gallons(): """Test conversion from gallons to other units.""" gallons = 5 assert volume_util.convert(gallons, VOLUME_GALLONS, VOLUME_LITERS) == 18.925 + + +def test_convert_from_cubic_meters(): + """Test conversion from cubic meter to other units.""" + cubic_meters = 5 + assert ( + volume_util.convert(cubic_meters, VOLUME_CUBIC_METERS, VOLUME_CUBIC_FEET) + == 176.5733335 + ) + + +def test_convert_from_cubic_feet(): + """Test conversion from cubic feet to cubic meters to other units.""" + cubic_feets = 500 + assert ( + volume_util.convert(cubic_feets, VOLUME_CUBIC_FEET, VOLUME_CUBIC_METERS) + == 14.1584233 + )