diff --git a/homeassistant/components/sensor/__init__.py b/homeassistant/components/sensor/__init__.py index e733cc877d1..ce488c3c76b 100644 --- a/homeassistant/components/sensor/__init__.py +++ b/homeassistant/components/sensor/__init__.py @@ -484,10 +484,19 @@ class SensorEntity(Entity): platform: EntityPlatform, parallel_updates: asyncio.Semaphore | None, ) -> None: - """Start adding an entity to a platform.""" + """Start adding an entity to a platform. + + Allows integrations to remove legacy custom unit conversion which is no longer + needed without breaking existing sensors. Only works for sensors which are in + the entity registry. + + This can be removed once core integrations have dropped unneeded custom unit + conversion. + """ super().add_to_platform_start(hass, platform, parallel_updates) - if self.unique_id is None: + # Bail out if the sensor doesn't have a unique_id or a device class + if self.unique_id is None or self.device_class is None: return registry = er.async_get(self.hass) if not ( @@ -499,24 +508,36 @@ class SensorEntity(Entity): registry_entry = registry.async_get(entity_id) assert registry_entry - # Store unit override according to automatic unit conversion rules if: - # - no unit override is stored in the entity registry - # - units have changed - # - the unit stored in the registry matches automatic unit conversion rules - # This allows integrations to drop custom unit conversion and rely on automatic - # conversion. + # If the sensor has 'unit_of_measurement' in its sensor options, the user has + # overridden the unit. + # If the sensor has 'sensor.private' in its entity options, it was added after + # automatic unit conversion was implemented. registry_unit = registry_entry.unit_of_measurement if ( - DOMAIN not in registry_entry.options - and f"{DOMAIN}.private" not in registry_entry.options - and self.unit_of_measurement != registry_unit - and (suggested_unit := self._get_initial_suggested_unit()) == registry_unit - ): - registry.async_update_entity_options( - entity_id, - f"{DOMAIN}.private", - {"suggested_unit_of_measurement": suggested_unit}, + ( + (sensor_options := registry_entry.options.get(DOMAIN)) + and CONF_UNIT_OF_MEASUREMENT in sensor_options ) + or f"{DOMAIN}.private" in registry_entry.options + or self.unit_of_measurement == registry_unit + ): + return + + # Make sure we can convert the units + if ( + (unit_converter := UNIT_CONVERTERS.get(self.device_class)) is None + or registry_unit not in unit_converter.VALID_UNITS + or self.unit_of_measurement not in unit_converter.VALID_UNITS + ): + return + + # Set suggested_unit_of_measurement to the old unit to enable automatic + # conversion + registry.async_update_entity_options( + entity_id, + f"{DOMAIN}.private", + {"suggested_unit_of_measurement": registry_unit}, + ) async def async_internal_added_to_hass(self) -> None: """Call when the sensor entity is added to hass.""" @@ -572,8 +593,12 @@ class SensorEntity(Entity): return None - def _get_initial_suggested_unit(self) -> str | None: - """Return initial suggested unit of measurement.""" + def get_initial_entity_options(self) -> er.EntityOptionsType | None: + """Return initial entity options. + + These will be stored in the entity registry the first time the entity is seen, + and then never updated. + """ # Unit suggested by the integration suggested_unit_of_measurement = self.suggested_unit_of_measurement @@ -583,15 +608,6 @@ class SensorEntity(Entity): self.device_class, self.native_unit_of_measurement ) - return suggested_unit_of_measurement - - def get_initial_entity_options(self) -> er.EntityOptionsType | None: - """Return initial entity options. - - These will be stored in the entity registry the first time the entity is seen, - and then never updated. - """ - suggested_unit_of_measurement = self._get_initial_suggested_unit() if suggested_unit_of_measurement is None: return None diff --git a/tests/components/sensor/test_init.py b/tests/components/sensor/test_init.py index 51e1c014fc9..3e68ba1a7fd 100644 --- a/tests/components/sensor/test_init.py +++ b/tests/components/sensor/test_init.py @@ -889,6 +889,14 @@ async def test_unit_conversion_priority_suggested_unit_change( 621, SensorDeviceClass.DISTANCE, ), + ( + US_CUSTOMARY_SYSTEM, + LENGTH_METERS, + LENGTH_MILES, + 1000000, + 621.371, + SensorDeviceClass.DISTANCE, + ), ], ) async def test_unit_conversion_priority_legacy_conversion_removed(