diff --git a/homeassistant/components/sensor/__init__.py b/homeassistant/components/sensor/__init__.py index eece39c793b..c9e8568c638 100644 --- a/homeassistant/components/sensor/__init__.py +++ b/homeassistant/components/sensor/__init__.py @@ -8,7 +8,7 @@ from dataclasses import dataclass from datetime import date, datetime, timedelta, timezone from decimal import Decimal, InvalidOperation as DecimalInvalidOperation import logging -from math import floor, log10 +from math import ceil, floor, log10 import re from typing import Any, Final, cast, final @@ -679,11 +679,40 @@ class SensorEntity(Entity): """Update suggested display precision stored in registry.""" assert self.registry_entry + device_class = self.device_class display_precision = self.suggested_display_precision + default_unit_of_measurement = ( + self.suggested_unit_of_measurement or self.native_unit_of_measurement + ) + unit_of_measurement = self.unit_of_measurement if ( - sensor_options := self.registry_entry.options.get(DOMAIN, {}) - ) and sensor_options.get("suggested_display_precision") == display_precision: + display_precision is not None + and default_unit_of_measurement != unit_of_measurement + and device_class in UNIT_CONVERTERS + ): + converter = UNIT_CONVERTERS[device_class] + + # Scale the precision when converting to a larger or smaller unit + # For example 1.1 Wh should be rendered as 0.0011 kWh, not 0.0 kWh + ratio_log = log10( + converter.get_unit_ratio( + default_unit_of_measurement, unit_of_measurement + ) + ) + ratio_log = floor(ratio_log) if ratio_log > 0 else ceil(ratio_log) + display_precision = max(0, display_precision + ratio_log) + + if display_precision is None and ( + DOMAIN not in self.registry_entry.options + or "suggested_display_precision" not in self.registry_entry.options + ): + return + sensor_options = self.registry_entry.options.get(DOMAIN, {}) + if ( + "suggested_display_precision" in sensor_options + and sensor_options["suggested_display_precision"] == display_precision + ): return registry = er.async_get(self.hass) @@ -716,6 +745,7 @@ class SensorEntity(Entity): def async_registry_entry_updated(self) -> None: """Run when the entity registry entry has been updated.""" self._async_read_entity_options() + self._update_suggested_precision() @callback def _async_read_entity_options(self) -> None: diff --git a/tests/components/sensor/test_init.py b/tests/components/sensor/test_init.py index 769c73ca0bd..5a6429fffa4 100644 --- a/tests/components/sensor/test_init.py +++ b/tests/components/sensor/test_init.py @@ -1143,27 +1143,46 @@ async def test_unit_conversion_priority_suggested_unit_change( @pytest.mark.parametrize( - "native_unit, suggested_precision, native_value, device_class", + "unit_system, native_unit, integration_suggested_precision," + "options_suggested_precision, native_value, device_class, extra_options", [ # Distance ( + METRIC_SYSTEM, UnitOfLength.KILOMETERS, 4, + 4, 1000, SensorDeviceClass.DISTANCE, + {}, + ), + # Air pressure + ( + US_CUSTOMARY_SYSTEM, + UnitOfPressure.HPA, + 0, + 1, + 1000, + SensorDeviceClass.ATMOSPHERIC_PRESSURE, + {"sensor.private": {"suggested_unit_of_measurement": "inHg"}}, ), ], ) async def test_suggested_precision_option( hass, enable_custom_integrations, + unit_system, native_unit, - suggested_precision, + integration_suggested_precision, + options_suggested_precision, native_value, device_class, + extra_options, ): """Test suggested precision is stored in the registry.""" + hass.config.units = unit_system + entity_registry = er.async_get(hass) platform = getattr(hass.components, "test.sensor") platform.init(empty=True) @@ -1173,7 +1192,7 @@ async def test_suggested_precision_option( device_class=device_class, native_unit_of_measurement=native_unit, native_value=str(native_value), - suggested_display_precision=suggested_precision, + suggested_display_precision=integration_suggested_precision, unique_id="very_unique", ) entity0 = platform.ENTITIES["0"] @@ -1183,34 +1202,58 @@ async def test_suggested_precision_option( # Assert the suggested precision is stored in the registry entry = entity_registry.async_get(entity0.entity_id) - assert entry.options == { - "sensor": {"suggested_display_precision": suggested_precision} + assert entry.options == extra_options | { + "sensor": {"suggested_display_precision": options_suggested_precision} } @pytest.mark.parametrize( - "native_unit, old_precision, new_precision, native_value, device_class", + "unit_system, native_unit, suggested_unit, old_precision, new_precision," + "opt_precision, native_value, device_class, extra_options", [ + # Distance ( + METRIC_SYSTEM, + UnitOfLength.KILOMETERS, UnitOfLength.KILOMETERS, 4, 1, + 1, 1000, SensorDeviceClass.DISTANCE, + {}, + ), + # Air pressure + ( + US_CUSTOMARY_SYSTEM, + UnitOfPressure.HPA, + UnitOfPressure.INHG, + 1, + 1, + 2, + 1000, + SensorDeviceClass.ATMOSPHERIC_PRESSURE, + {"sensor.private": {"suggested_unit_of_measurement": "inHg"}}, ), ], ) async def test_suggested_precision_option_update( hass, enable_custom_integrations, + unit_system, native_unit, + suggested_unit, old_precision, new_precision, + opt_precision, native_value, device_class, + extra_options, ): """Test suggested precision stored in the registry is updated.""" + hass.config.units = unit_system + entity_registry = er.async_get(hass) platform = getattr(hass.components, "test.sensor") platform.init(empty=True) @@ -1228,7 +1271,7 @@ async def test_suggested_precision_option_update( entry.entity_id, "sensor.private", { - "suggested_unit_of_measurement": native_unit, + "suggested_unit_of_measurement": suggested_unit, }, ) @@ -1249,10 +1292,10 @@ async def test_suggested_precision_option_update( entry = entity_registry.async_get(entity0.entity_id) assert entry.options == { "sensor": { - "suggested_display_precision": new_precision, + "suggested_display_precision": opt_precision, }, "sensor.private": { - "suggested_unit_of_measurement": native_unit, + "suggested_unit_of_measurement": suggested_unit, }, }