Add default device class display precision for Sensor (#145013)

* Add default device class display precision for Sensor

* Renaming, docstrings, cleanup

* Simplify units list

* Fix tests

* Fix missing precision when suggested is specified

* Update snapshots

* Fix when unit of measurement is not valid

* Fix tests

* Fix deprecated unit usage

* Fix goalzero tests

The sensor native_value method was accessing the data dict and trowing,
since the mock did not have any data for the sensors.

Since now the precision is always specified (it was missing for those
sensors), the throw was hitting async_update_entity_options in _update_suggested_precision.
Previously, async_update_entity_options was not called since it had no
precision.

* Fix metoffice

* Fix smartthings

* Add default sensor data for Tesla Wall Connector tests

* Update snapshots

* Revert spaces

* Update smartthings snapshots

* Add missing sensor mock for tesla wall connector

* Address review comments

* Add doc comment

* Add cap to doc comment

* Update comment

* Update snapshots

* Update comment
This commit is contained in:
Abílio Costa
2025-05-26 18:40:29 +01:00
committed by GitHub
parent b15989f2bf
commit b626204f63
123 changed files with 4523 additions and 377 deletions

View File

@@ -31,12 +31,25 @@ from homeassistant.const import (
PERCENTAGE,
STATE_UNKNOWN,
EntityCategory,
UnitOfApparentPower,
UnitOfArea,
UnitOfBloodGlucoseConcentration,
UnitOfConductivity,
UnitOfDataRate,
UnitOfElectricCurrent,
UnitOfElectricPotential,
UnitOfEnergy,
UnitOfEnergyDistance,
UnitOfFrequency,
UnitOfInformation,
UnitOfIrradiance,
UnitOfLength,
UnitOfMass,
UnitOfPower,
UnitOfPrecipitationDepth,
UnitOfPressure,
UnitOfReactivePower,
UnitOfSoundPressure,
UnitOfSpeed,
UnitOfTemperature,
UnitOfTime,
@@ -78,28 +91,28 @@ TEST_DOMAIN = "test"
UnitOfTemperature.FAHRENHEIT,
UnitOfTemperature.FAHRENHEIT,
100,
"100",
100,
),
(
US_CUSTOMARY_SYSTEM,
UnitOfTemperature.CELSIUS,
UnitOfTemperature.FAHRENHEIT,
38,
"100",
100.4,
),
(
METRIC_SYSTEM,
UnitOfTemperature.FAHRENHEIT,
UnitOfTemperature.CELSIUS,
100,
"38",
pytest.approx(37.77778),
),
(
METRIC_SYSTEM,
UnitOfTemperature.CELSIUS,
UnitOfTemperature.CELSIUS,
38,
"38",
38,
),
],
)
@@ -125,7 +138,7 @@ async def test_temperature_conversion(
await hass.async_block_till_done()
state = hass.states.get(entity0.entity_id)
assert state.state == state_value
assert float(state.state) == state_value
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == state_unit
@@ -593,6 +606,8 @@ async def test_unit_translation_key_without_platform_raises(
"state_unit",
"native_value",
"custom_state",
"rounded_state",
"suggested_precision",
),
[
# Smaller to larger unit, InHg is ~33x larger than hPa -> 1 more decimal
@@ -602,7 +617,9 @@ async def test_unit_translation_key_without_platform_raises(
UnitOfPressure.INHG,
UnitOfPressure.INHG,
1000.0,
pytest.approx(29.52998),
"29.53",
2,
),
(
SensorDeviceClass.PRESSURE,
@@ -610,7 +627,19 @@ async def test_unit_translation_key_without_platform_raises(
UnitOfPressure.HPA,
UnitOfPressure.HPA,
1.234,
"12.340",
12.34,
"12.34",
2,
),
(
SensorDeviceClass.PRESSURE,
UnitOfPressure.HPA,
UnitOfPressure.PA,
UnitOfPressure.PA,
1.234,
123.4,
"123",
0,
),
(
SensorDeviceClass.ATMOSPHERIC_PRESSURE,
@@ -618,7 +647,9 @@ async def test_unit_translation_key_without_platform_raises(
UnitOfPressure.MMHG,
UnitOfPressure.MMHG,
1000,
"750",
pytest.approx(750.061575),
"750.06",
2,
),
(
SensorDeviceClass.PRESSURE,
@@ -626,7 +657,9 @@ async def test_unit_translation_key_without_platform_raises(
UnitOfPressure.MMHG,
UnitOfPressure.MMHG,
1000,
"750",
pytest.approx(750.061575),
"750.06",
2,
),
# Not a supported pressure unit
(
@@ -635,7 +668,9 @@ async def test_unit_translation_key_without_platform_raises(
"peer_pressure",
UnitOfPressure.HPA,
1000,
"1000",
1000,
"1000.00",
2,
),
(
SensorDeviceClass.TEMPERATURE,
@@ -643,7 +678,9 @@ async def test_unit_translation_key_without_platform_raises(
UnitOfTemperature.FAHRENHEIT,
UnitOfTemperature.FAHRENHEIT,
37.5,
99.5,
"99.5",
1,
),
(
SensorDeviceClass.TEMPERATURE,
@@ -651,7 +688,9 @@ async def test_unit_translation_key_without_platform_raises(
UnitOfTemperature.CELSIUS,
UnitOfTemperature.CELSIUS,
100,
"38",
pytest.approx(37.77777),
"37.8",
1,
),
(
SensorDeviceClass.ATMOSPHERIC_PRESSURE,
@@ -659,7 +698,9 @@ async def test_unit_translation_key_without_platform_raises(
UnitOfPressure.HPA,
UnitOfPressure.HPA,
-0.00,
"0.0",
0.0,
"0.00",
2,
),
(
SensorDeviceClass.ATMOSPHERIC_PRESSURE,
@@ -667,7 +708,9 @@ async def test_unit_translation_key_without_platform_raises(
UnitOfPressure.HPA,
UnitOfPressure.HPA,
-0.00001,
"0",
pytest.approx(-0.0003386388),
"0.00",
2,
),
(
SensorDeviceClass.VOLUME_FLOW_RATE,
@@ -675,7 +718,9 @@ async def test_unit_translation_key_without_platform_raises(
UnitOfVolumeFlowRate.GALLONS_PER_MINUTE,
UnitOfVolumeFlowRate.GALLONS_PER_MINUTE,
50.0,
"13.2",
pytest.approx(13.208602),
"13",
0,
),
(
SensorDeviceClass.VOLUME_FLOW_RATE,
@@ -683,7 +728,9 @@ async def test_unit_translation_key_without_platform_raises(
UnitOfVolumeFlowRate.LITERS_PER_MINUTE,
UnitOfVolumeFlowRate.LITERS_PER_MINUTE,
13.0,
"49.2",
pytest.approx(49.2103531),
"49",
0,
),
(
SensorDeviceClass.DURATION,
@@ -691,7 +738,9 @@ async def test_unit_translation_key_without_platform_raises(
UnitOfTime.HOURS,
UnitOfTime.HOURS,
5400.0,
"1.5000",
1.5,
"1.50",
2,
),
(
SensorDeviceClass.DURATION,
@@ -699,7 +748,29 @@ async def test_unit_translation_key_without_platform_raises(
UnitOfTime.MINUTES,
UnitOfTime.MINUTES,
0.5,
"720.0",
720,
"720.00",
2,
),
(
SensorDeviceClass.BLOOD_GLUCOSE_CONCENTRATION,
UnitOfBloodGlucoseConcentration.MILLIGRAMS_PER_DECILITER,
UnitOfBloodGlucoseConcentration.MILLIMOLE_PER_LITER,
UnitOfBloodGlucoseConcentration.MILLIMOLE_PER_LITER,
130,
pytest.approx(7.222222),
"7.2",
1,
),
(
SensorDeviceClass.ENERGY,
UnitOfEnergy.WATT_HOUR,
UnitOfEnergy.KILO_WATT_HOUR,
UnitOfEnergy.KILO_WATT_HOUR,
1.1,
0.0011,
"0.00",
2,
),
],
)
@@ -712,6 +783,8 @@ async def test_custom_unit(
state_unit,
native_value,
custom_state,
rounded_state,
suggested_precision,
) -> None:
"""Test custom unit."""
entry = entity_registry.async_get_or_create("sensor", "test", "very_unique")
@@ -734,13 +807,17 @@ async def test_custom_unit(
entity_id = entity0.entity_id
state = hass.states.get(entity_id)
assert state.state == custom_state
assert float(state.state) == custom_state
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == state_unit
assert (
async_rounded_state(hass, entity_id, hass.states.get(entity_id)) == custom_state
async_rounded_state(hass, entity_id, hass.states.get(entity_id))
== rounded_state
)
entry = entity_registry.async_get(entity0.entity_id)
assert entry.options["sensor"]["suggested_display_precision"] == suggested_precision
@pytest.mark.parametrize(
(
@@ -759,8 +836,8 @@ async def test_custom_unit(
UnitOfArea.SQUARE_MILES,
UnitOfArea.SQUARE_MILES,
1000,
"1000",
"386",
1000,
pytest.approx(386.102),
SensorDeviceClass.AREA,
),
(
@@ -768,8 +845,8 @@ async def test_custom_unit(
UnitOfArea.SQUARE_INCHES,
UnitOfArea.SQUARE_INCHES,
7.24,
"7.24",
"1.12",
7.24,
pytest.approx(1.1222022),
SensorDeviceClass.AREA,
),
(
@@ -777,8 +854,8 @@ async def test_custom_unit(
"peer_distance",
UnitOfArea.SQUARE_KILOMETERS,
1000,
"1000",
"1000",
1000,
1000,
SensorDeviceClass.AREA,
),
# Distance
@@ -787,8 +864,8 @@ async def test_custom_unit(
UnitOfLength.MILES,
UnitOfLength.MILES,
1000,
"1000",
"621",
1000,
pytest.approx(621.371),
SensorDeviceClass.DISTANCE,
),
(
@@ -796,8 +873,8 @@ async def test_custom_unit(
UnitOfLength.INCHES,
UnitOfLength.INCHES,
7.24,
"7.24",
"2.85",
7.24,
pytest.approx(2.8503937),
SensorDeviceClass.DISTANCE,
),
(
@@ -805,8 +882,8 @@ async def test_custom_unit(
"peer_distance",
UnitOfLength.KILOMETERS,
1000,
"1000",
"1000",
1000,
1000,
SensorDeviceClass.DISTANCE,
),
# Energy
@@ -815,8 +892,8 @@ async def test_custom_unit(
UnitOfEnergy.MEGA_WATT_HOUR,
UnitOfEnergy.MEGA_WATT_HOUR,
1000,
"1000",
"1.000",
1000,
1.000,
SensorDeviceClass.ENERGY,
),
(
@@ -824,8 +901,8 @@ async def test_custom_unit(
UnitOfEnergy.MEGA_WATT_HOUR,
UnitOfEnergy.MEGA_WATT_HOUR,
1000,
"1000",
"278",
1000,
pytest.approx(277.7778),
SensorDeviceClass.ENERGY,
),
(
@@ -833,8 +910,8 @@ async def test_custom_unit(
"BTU",
UnitOfEnergy.KILO_WATT_HOUR,
1000,
"1000",
"1000",
1000,
1000,
SensorDeviceClass.ENERGY,
),
# Power factor
@@ -843,8 +920,8 @@ async def test_custom_unit(
PERCENTAGE,
PERCENTAGE,
1.0,
"1.0",
"100.0",
1.0,
100.0,
SensorDeviceClass.POWER_FACTOR,
),
(
@@ -852,8 +929,8 @@ async def test_custom_unit(
None,
None,
100,
"100",
"1.00",
100,
1.00,
SensorDeviceClass.POWER_FACTOR,
),
(
@@ -861,8 +938,8 @@ async def test_custom_unit(
None,
"Cos φ",
1.0,
"1.0",
"1.0",
1.0,
1.0,
SensorDeviceClass.POWER_FACTOR,
),
# Pressure
@@ -872,8 +949,8 @@ async def test_custom_unit(
UnitOfPressure.INHG,
UnitOfPressure.INHG,
1000.0,
"1000.0",
"29.53",
1000.0,
pytest.approx(29.52998),
SensorDeviceClass.PRESSURE,
),
(
@@ -881,8 +958,8 @@ async def test_custom_unit(
UnitOfPressure.HPA,
UnitOfPressure.HPA,
1.234,
"1.234",
"12.340",
1.234,
12.340,
SensorDeviceClass.PRESSURE,
),
(
@@ -890,8 +967,8 @@ async def test_custom_unit(
UnitOfPressure.MMHG,
UnitOfPressure.MMHG,
1000,
"1000",
"750",
1000,
pytest.approx(750.0615),
SensorDeviceClass.PRESSURE,
),
# Not a supported pressure unit
@@ -900,8 +977,8 @@ async def test_custom_unit(
"peer_pressure",
UnitOfPressure.HPA,
1000,
"1000",
"1000",
1000,
1000,
SensorDeviceClass.PRESSURE,
),
# Speed
@@ -910,8 +987,8 @@ async def test_custom_unit(
UnitOfSpeed.MILES_PER_HOUR,
UnitOfSpeed.MILES_PER_HOUR,
100,
"100",
"62",
100,
pytest.approx(62.1371),
SensorDeviceClass.SPEED,
),
(
@@ -919,8 +996,8 @@ async def test_custom_unit(
UnitOfVolumetricFlux.INCHES_PER_HOUR,
UnitOfVolumetricFlux.INCHES_PER_HOUR,
78,
"78",
"0.13",
78,
pytest.approx(0.127952755),
SensorDeviceClass.SPEED,
),
(
@@ -928,8 +1005,8 @@ async def test_custom_unit(
"peer_distance",
UnitOfSpeed.KILOMETERS_PER_HOUR,
100,
"100",
"100",
100,
100,
SensorDeviceClass.SPEED,
),
# Volume
@@ -938,8 +1015,8 @@ async def test_custom_unit(
UnitOfVolume.CUBIC_FEET,
UnitOfVolume.CUBIC_FEET,
100,
"100",
"3531",
100,
pytest.approx(3531.4667),
SensorDeviceClass.VOLUME,
),
(
@@ -947,8 +1024,8 @@ async def test_custom_unit(
UnitOfVolume.FLUID_OUNCES,
UnitOfVolume.FLUID_OUNCES,
2.3,
"2.3",
"77.8",
2.3,
pytest.approx(77.77225),
SensorDeviceClass.VOLUME,
),
(
@@ -956,8 +1033,8 @@ async def test_custom_unit(
"peer_distance",
UnitOfVolume.CUBIC_METERS,
100,
"100",
"100",
100,
100,
SensorDeviceClass.VOLUME,
),
# Weight
@@ -966,8 +1043,8 @@ async def test_custom_unit(
UnitOfMass.OUNCES,
UnitOfMass.OUNCES,
100,
"100",
"3.5",
100,
pytest.approx(3.5273962),
SensorDeviceClass.WEIGHT,
),
(
@@ -975,8 +1052,8 @@ async def test_custom_unit(
UnitOfMass.GRAMS,
UnitOfMass.GRAMS,
78,
"78",
"2211",
78,
pytest.approx(2211.262),
SensorDeviceClass.WEIGHT,
),
(
@@ -984,8 +1061,8 @@ async def test_custom_unit(
"peer_distance",
UnitOfMass.GRAMS,
100,
"100",
"100",
100,
100,
SensorDeviceClass.WEIGHT,
),
],
@@ -1015,7 +1092,7 @@ async def test_custom_unit_change(
await hass.async_block_till_done()
state = hass.states.get(entity0.entity_id)
assert state.state == native_state
assert float(state.state) == native_state
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == native_unit
entity_registry.async_update_entity_options(
@@ -1024,7 +1101,7 @@ async def test_custom_unit_change(
await hass.async_block_till_done()
state = hass.states.get(entity0.entity_id)
assert state.state == custom_state
assert float(state.state) == custom_state
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == state_unit
entity_registry.async_update_entity_options(
@@ -1033,14 +1110,14 @@ async def test_custom_unit_change(
await hass.async_block_till_done()
state = hass.states.get(entity0.entity_id)
assert state.state == native_state
assert float(state.state) == native_state
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == native_unit
entity_registry.async_update_entity_options("sensor.test", "sensor", None)
await hass.async_block_till_done()
state = hass.states.get(entity0.entity_id)
assert state.state == native_state
assert float(state.state) == native_state
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == native_unit
@@ -1067,10 +1144,10 @@ async def test_custom_unit_change(
UnitOfLength.METERS,
UnitOfLength.YARDS,
1000,
"1000",
"621",
"1000000",
"1093613",
1000,
pytest.approx(621.371),
1000000,
pytest.approx(1093613),
SensorDeviceClass.DISTANCE,
),
# Volume Storage (subclass of Volume)
@@ -1081,10 +1158,10 @@ async def test_custom_unit_change(
UnitOfVolume.GALLONS,
UnitOfVolume.FLUID_OUNCES,
1000,
"1000",
"264",
"264",
"33814",
1000,
pytest.approx(264.172),
pytest.approx(264.172),
pytest.approx(33814.022),
SensorDeviceClass.VOLUME_STORAGE,
),
],
@@ -1152,34 +1229,36 @@ async def test_unit_conversion_priority(
# Registered entity -> Follow automatic unit conversion
state = hass.states.get(entity0.entity_id)
assert state.state == automatic_state
assert float(state.state) == automatic_state
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == automatic_unit
# Assert the automatic unit conversion is stored in the registry
entry = entity_registry.async_get(entity0.entity_id)
assert entry.unit_of_measurement == automatic_unit
assert entry.options == {
"sensor.private": {"suggested_unit_of_measurement": automatic_unit}
}
assert (
entry.options["sensor.private"]["suggested_unit_of_measurement"]
== automatic_unit
)
# Unregistered entity -> Follow native unit
state = hass.states.get(entity1.entity_id)
assert state.state == native_state
assert float(state.state) == native_state
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == native_unit
# Registered entity with suggested unit
state = hass.states.get(entity2.entity_id)
assert state.state == suggested_state
assert float(state.state) == suggested_state
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == suggested_unit
# Assert the suggested unit is stored in the registry
entry = entity_registry.async_get(entity2.entity_id)
assert entry.unit_of_measurement == suggested_unit
assert entry.options == {
"sensor.private": {"suggested_unit_of_measurement": suggested_unit}
}
assert (
entry.options["sensor.private"]["suggested_unit_of_measurement"]
== suggested_unit
)
# Unregistered entity with suggested unit
state = hass.states.get(entity3.entity_id)
assert state.state == suggested_state
assert float(state.state) == suggested_state
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == suggested_unit
# Set a custom unit, this should have priority over the automatic unit conversion
@@ -1189,7 +1268,7 @@ async def test_unit_conversion_priority(
await hass.async_block_till_done()
state = hass.states.get(entity0.entity_id)
assert state.state == custom_state
assert float(state.state) == custom_state
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == custom_unit
entity_registry.async_update_entity_options(
@@ -1198,7 +1277,7 @@ async def test_unit_conversion_priority(
await hass.async_block_till_done()
state = hass.states.get(entity2.entity_id)
assert state.state == custom_state
assert float(state.state) == custom_state
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == custom_unit
@@ -1387,7 +1466,6 @@ async def test_unit_conversion_priority_precision(
{"display_precision": 4},
)
entry4 = entity_registry.async_get(entity4.entity_id)
assert "suggested_display_precision" not in entry4.options["sensor"]
assert entry4.options["sensor"]["display_precision"] == 4
await hass.async_block_till_done()
state = hass.states.get(entity4.entity_id)
@@ -1479,9 +1557,10 @@ async def test_unit_conversion_priority_suggested_unit_change(
# Assert the suggested unit is stored in the registry
entry = entity_registry.async_get(entity0.entity_id)
assert entry.unit_of_measurement == original_unit
assert entry.options == {
"sensor.private": {"suggested_unit_of_measurement": original_unit},
}
assert (
entry.options["sensor.private"]["suggested_unit_of_measurement"]
== original_unit
)
# Registered entity -> Follow suggested unit the first time the entity was seen
state = hass.states.get(entity1.entity_id)
@@ -1490,9 +1569,10 @@ async def test_unit_conversion_priority_suggested_unit_change(
# Assert the suggested unit is stored in the registry
entry = entity_registry.async_get(entity1.entity_id)
assert entry.unit_of_measurement == original_unit
assert entry.options == {
"sensor.private": {"suggested_unit_of_measurement": original_unit},
}
assert (
entry.options["sensor.private"]["suggested_unit_of_measurement"]
== original_unit
)
@pytest.mark.parametrize(
@@ -1574,9 +1654,10 @@ async def test_unit_conversion_priority_suggested_unit_change_2(
# Assert the suggested unit is stored in the registry
entry = entity_registry.async_get(entity0.entity_id)
assert entry.unit_of_measurement == native_unit_1
assert entry.options == {
"sensor.private": {"suggested_unit_of_measurement": native_unit_1},
}
assert (
entry.options["sensor.private"]["suggested_unit_of_measurement"]
== native_unit_1
)
# Registered entity -> Follow unit in entity registry
state = hass.states.get(entity1.entity_id)
@@ -1585,9 +1666,89 @@ async def test_unit_conversion_priority_suggested_unit_change_2(
# Assert the suggested unit is stored in the registry
entry = entity_registry.async_get(entity0.entity_id)
assert entry.unit_of_measurement == native_unit_1
assert entry.options == {
"sensor.private": {"suggested_unit_of_measurement": native_unit_1},
}
assert (
entry.options["sensor.private"]["suggested_unit_of_measurement"]
== native_unit_1
)
@pytest.mark.parametrize(
(
"device_class",
"native_unit",
"suggested_precision",
),
[
(SensorDeviceClass.APPARENT_POWER, UnitOfApparentPower.VOLT_AMPERE, 0),
(SensorDeviceClass.AREA, UnitOfArea.SQUARE_CENTIMETERS, 0),
(SensorDeviceClass.ATMOSPHERIC_PRESSURE, UnitOfPressure.PA, 0),
(
SensorDeviceClass.BLOOD_GLUCOSE_CONCENTRATION,
UnitOfBloodGlucoseConcentration.MILLIGRAMS_PER_DECILITER,
0,
),
(SensorDeviceClass.CONDUCTIVITY, UnitOfConductivity.MICROSIEMENS, 1),
(SensorDeviceClass.CURRENT, UnitOfElectricCurrent.MILLIAMPERE, 0),
(SensorDeviceClass.DATA_RATE, UnitOfDataRate.KILOBITS_PER_SECOND, 0),
(SensorDeviceClass.DATA_SIZE, UnitOfInformation.KILOBITS, 0),
(SensorDeviceClass.DISTANCE, UnitOfLength.CENTIMETERS, 0),
(SensorDeviceClass.DURATION, UnitOfTime.MILLISECONDS, 0),
(SensorDeviceClass.ENERGY, UnitOfEnergy.WATT_HOUR, 0),
(
SensorDeviceClass.ENERGY_DISTANCE,
UnitOfEnergyDistance.KM_PER_KILO_WATT_HOUR,
0,
),
(SensorDeviceClass.ENERGY_STORAGE, UnitOfEnergy.WATT_HOUR, 0),
(SensorDeviceClass.FREQUENCY, UnitOfFrequency.HERTZ, 0),
(SensorDeviceClass.GAS, UnitOfVolume.MILLILITERS, 0),
(SensorDeviceClass.IRRADIANCE, UnitOfIrradiance.WATTS_PER_SQUARE_METER, 0),
(SensorDeviceClass.POWER, UnitOfPower.WATT, 0),
(SensorDeviceClass.PRECIPITATION, UnitOfPrecipitationDepth.CENTIMETERS, 0),
(
SensorDeviceClass.PRECIPITATION_INTENSITY,
UnitOfVolumetricFlux.MILLIMETERS_PER_HOUR,
0,
),
(SensorDeviceClass.PRESSURE, UnitOfPressure.PA, 0),
(SensorDeviceClass.REACTIVE_POWER, UnitOfReactivePower.VOLT_AMPERE_REACTIVE, 0),
(SensorDeviceClass.SOUND_PRESSURE, UnitOfSoundPressure.DECIBEL, 0),
(SensorDeviceClass.SPEED, UnitOfSpeed.MILLIMETERS_PER_SECOND, 0),
(SensorDeviceClass.TEMPERATURE, UnitOfTemperature.KELVIN, 1),
(SensorDeviceClass.VOLTAGE, UnitOfElectricPotential.VOLT, 0),
(SensorDeviceClass.VOLUME, UnitOfVolume.MILLILITERS, 0),
(SensorDeviceClass.VOLUME_FLOW_RATE, UnitOfVolumeFlowRate.LITERS_PER_SECOND, 0),
(SensorDeviceClass.VOLUME_STORAGE, UnitOfVolume.MILLILITERS, 0),
(SensorDeviceClass.WATER, UnitOfVolume.MILLILITERS, 0),
(SensorDeviceClass.WEIGHT, UnitOfMass.GRAMS, 0),
(SensorDeviceClass.WIND_SPEED, UnitOfSpeed.MILLIMETERS_PER_SECOND, 0),
],
)
async def test_default_precision(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
device_class: str,
native_unit: str,
suggested_precision: int,
) -> None:
"""Test default unit precision."""
entry = entity_registry.async_get_or_create("sensor", "test", "very_unique")
await hass.async_block_till_done()
entity0 = MockSensor(
name="Test",
native_value="123",
native_unit_of_measurement=native_unit,
device_class=device_class,
unique_id="very_unique",
)
setup_test_component_platform(hass, sensor.DOMAIN, [entity0])
assert await async_setup_component(hass, "sensor", {"sensor": {"platform": "test"}})
await hass.async_block_till_done()
entry = entity_registry.async_get(entity0.entity_id)
assert entry.options["sensor"]["suggested_display_precision"] == suggested_precision
@pytest.mark.parametrize(
@@ -1756,39 +1917,6 @@ async def test_suggested_precision_option_update(
}
async def test_suggested_precision_option_removal(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
) -> None:
"""Test suggested precision stored in the registry is removed."""
# Pre-register entities
entry = entity_registry.async_get_or_create("sensor", "test", "very_unique")
entity_registry.async_update_entity_options(
entry.entity_id,
"sensor",
{
"suggested_display_precision": 1,
},
)
entity0 = MockSensor(
name="Test",
device_class=SensorDeviceClass.DURATION,
native_unit_of_measurement=UnitOfTime.HOURS,
native_value="1.5",
suggested_display_precision=None,
unique_id="very_unique",
)
setup_test_component_platform(hass, sensor.DOMAIN, [entity0])
assert await async_setup_component(hass, "sensor", {"sensor": {"platform": "test"}})
await hass.async_block_till_done()
# Assert the suggested precision is no longer stored in the registry
entry = entity_registry.async_get(entity0.entity_id)
assert entry.options.get("sensor", {}).get("suggested_display_precision") is None
@pytest.mark.parametrize(
(
"unit_system",
@@ -1805,7 +1933,7 @@ async def test_suggested_precision_option_removal(
UnitOfLength.KILOMETERS,
UnitOfLength.MILES,
1000,
621.0,
621.3711,
SensorDeviceClass.DISTANCE,
),
(
@@ -2346,10 +2474,10 @@ async def test_numeric_state_expected_helper(
UnitOfLength.METERS,
UnitOfLength.YARDS,
1000,
"621",
"1000",
"1000000",
"1093613",
pytest.approx(621.3711),
1000,
1000000,
pytest.approx(1093613),
SensorDeviceClass.DISTANCE,
),
],
@@ -2439,40 +2567,40 @@ async def test_unit_conversion_update(
# Registered entity -> Follow automatic unit conversion
state = hass.states.get(entity0.entity_id)
assert state.state == automatic_state_1
assert float(state.state) == automatic_state_1
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == automatic_unit_1
# Assert the automatic unit conversion is stored in the registry
entry = entity_registry.async_get(entity0.entity_id)
assert entry.options == {
"sensor.private": {"suggested_unit_of_measurement": automatic_unit_1}
assert entry.options["sensor.private"] == {
"suggested_unit_of_measurement": automatic_unit_1
}
state = hass.states.get(entity1.entity_id)
assert state.state == automatic_state_1
assert float(state.state) == automatic_state_1
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == automatic_unit_1
# Assert the automatic unit conversion is stored in the registry
entry = entity_registry.async_get(entity1.entity_id)
assert entry.options == {
"sensor.private": {"suggested_unit_of_measurement": automatic_unit_1}
assert entry.options["sensor.private"] == {
"suggested_unit_of_measurement": automatic_unit_1
}
# Registered entity with suggested unit
state = hass.states.get(entity2.entity_id)
assert state.state == suggested_state
assert float(state.state) == suggested_state
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == suggested_unit
# Assert the suggested unit is stored in the registry
entry = entity_registry.async_get(entity2.entity_id)
assert entry.options == {
"sensor.private": {"suggested_unit_of_measurement": suggested_unit}
assert entry.options["sensor.private"] == {
"suggested_unit_of_measurement": suggested_unit
}
state = hass.states.get(entity3.entity_id)
assert state.state == suggested_state
assert float(state.state) == suggested_state
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == suggested_unit
# Assert the suggested unit is stored in the registry
entry = entity_registry.async_get(entity3.entity_id)
assert entry.options == {
"sensor.private": {"suggested_unit_of_measurement": suggested_unit}
assert entry.options["sensor.private"] == {
"suggested_unit_of_measurement": suggested_unit
}
# Set a custom unit, this should have priority over the automatic unit conversion
@@ -2482,7 +2610,7 @@ async def test_unit_conversion_update(
await hass.async_block_till_done()
state = hass.states.get(entity0.entity_id)
assert state.state == custom_state
assert float(state.state) == custom_state
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == custom_unit
entity_registry.async_update_entity_options(
@@ -2491,7 +2619,7 @@ async def test_unit_conversion_update(
await hass.async_block_till_done()
state = hass.states.get(entity2.entity_id)
assert state.state == custom_state
assert float(state.state) == custom_state
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == custom_unit
# Change unit system, states and units should be unchanged
@@ -2499,19 +2627,19 @@ async def test_unit_conversion_update(
await hass.async_block_till_done()
state = hass.states.get(entity0.entity_id)
assert state.state == custom_state
assert float(state.state) == custom_state
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == custom_unit
state = hass.states.get(entity1.entity_id)
assert state.state == automatic_state_1
assert float(state.state) == automatic_state_1
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == automatic_unit_1
state = hass.states.get(entity2.entity_id)
assert state.state == custom_state
assert float(state.state) == custom_state
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == custom_unit
state = hass.states.get(entity3.entity_id)
assert state.state == suggested_state
assert float(state.state) == suggested_state
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == suggested_unit
# Update suggested unit
@@ -2522,39 +2650,37 @@ async def test_unit_conversion_update(
await hass.async_block_till_done()
state = hass.states.get(entity0.entity_id)
assert state.state == custom_state
assert float(state.state) == custom_state
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == custom_unit
state = hass.states.get(entity1.entity_id)
assert state.state == automatic_state_2
assert float(state.state) == automatic_state_2
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == automatic_unit_2
state = hass.states.get(entity2.entity_id)
assert state.state == custom_state
assert float(state.state) == custom_state
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == custom_unit
state = hass.states.get(entity3.entity_id)
assert state.state == suggested_state
assert float(state.state) == suggested_state
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == suggested_unit
# Entity 4 still has a pending request to refresh entity options
entry = entity_registry.async_get(entity4_entity_id)
assert entry.options == {
"sensor.private": {
"refresh_initial_entity_options": True,
"suggested_unit_of_measurement": automatic_unit_1,
}
assert entry.options["sensor.private"] == {
"refresh_initial_entity_options": True,
"suggested_unit_of_measurement": automatic_unit_1,
}
# Add entity 4, the pending request to refresh entity options should be handled
await entity_platform.async_add_entities((entity4,))
state = hass.states.get(entity4_entity_id)
assert state.state == automatic_state_2
assert float(state.state) == automatic_state_2
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == automatic_unit_2
entry = entity_registry.async_get(entity4_entity_id)
assert entry.options == {}
assert "sensor.private" not in entry.options
class MockFlow(ConfigFlow):
@@ -2763,7 +2889,7 @@ async def test_suggested_unit_guard_invalid_unit(
UnitOfTemperature.CELSIUS,
10,
UnitOfTemperature.KELVIN,
283,
283.15,
),
(
SensorDeviceClass.DATA_RATE,
@@ -2809,8 +2935,8 @@ async def test_suggested_unit_guard_valid_unit(
# Assert the suggested unit of measurement is stored in the registry
entry = entity_registry.async_get(entity.entity_id)
assert entry.unit_of_measurement == suggested_unit
assert entry.options == {
"sensor.private": {"suggested_unit_of_measurement": suggested_unit},
assert entry.options["sensor.private"] == {
"suggested_unit_of_measurement": suggested_unit
}