Repository: home-assistant/core
Pull request: Fix suggested UOM cannot be set for dsmr entities
<!--
  You are amazing! Thanks for contributing to our project!
  Please, DO NOT DELETE ANY TEXT from this template! (unless instructed).
-->
## Proposed change
<!--
  Describe the big picture of your changes here to communicate to the
  maintainers why we should accept this pull request. If it fixes a bug
  or resolves a feature request, be sure to link to that issue in the
  additional information section.
-->
Fix suggested UOM cannot be set for dsmr entities
This is an alternative approach to #102045.
This PR adds Supply entities just-in-time on first telegram received instead of adding them based on sensor descriptions.
This fixes issues with changing the suggested UOM.
Note that sensors will only be added if the corresponding OBIS service is detected in the (first) telegram, this avoids sensors being added that are not supported by the meter.

## Type of change
<!--
  What type of change does your PR introduce to Home Assistant?
  NOTE: Please, check only 1! box!
  If your PR requires multiple boxes to be checked, you'll most likely need to
  split it into multiple PRs. This makes things easier and faster to code review.
-->

- [ ] Dependency upgrade
- [x] Bugfix (non-breaking change which fixes an issue)
- [ ] New integration (thank you!)
- [ ] New feature (which adds functionality to an existing integration)
- [ ] Deprecation (breaking change to happen in the future)
- [ ] Breaking change (fix/feature causing existing functionality to break)
- [ ] Code quality improvements to existing code or addition of tests

## Additional information
<!--
  Details are important, and help maintainers processing your PR.
  Please be sure to fill out additional details, if applicable.
-->

- This PR fixes or closes issue: fixes #92492 
- This PR is related to issue: 
- Link to documentation pull request: 

## Checklist
<!--
  Put an `x` in the boxes that apply. You can also fill these out after
  creating the PR. If you're unsure about any of them, don't hesitate to ask.
  We're here to help! This is simply a reminder of what we are going to look
  for before merging your code.
-->

- [ ] The code change is tested and works locally.
- [ ] Local tests pass. **Your PR cannot be merged unless tests pass**
- [ ] There is no commented out code in this PR.
- [ ] I have followed the [development checklist][dev-checklist]
- [ ] I have followed the [perfect PR recommendations][perfect-pr]
- [ ] The code has been formatted using Black (`black --fast homeassistant tests`)
- [ ] Tests have been added to verify that the new code works.

If user exposed functionality or configuration variables are added/changed:

- [ ] Documentation added/updated for [www.home-assistant.io][docs-repository]

If the code communicates with devices, web services, or third-party tools:

- [ ] The [manifest file][manifest-docs] has all fields filled out correctly.  
      Updated and included derived files by running: `python3 -m script.hassfest`.
- [ ] New or updated dependencies have been added to `requirements_all.txt`.  
      Updated by running `python3 -m script.gen_requirements_all`.
- [ ] For the updated dependencies - a link to the changelog, or at minimum a diff between library versions is added to the PR description.
- [ ] Untested files have been added to `.coveragerc`.

<!--
  This project is very active and we have a high turnover of pull requests.

  Unfortunately, the number of incoming pull requests is higher than what our
  reviewers can review and merge so there is a long backlog of pull requests
  waiting for review. You can help here!
  
  By reviewing another pull request, you will help raise the code quality of
  that pull request and the final review will be faster. This way the general
  pace of pull request reviews will go up and your wait time will go down.
  
  When picking a pull request to review, try to choose one that hasn't yet
  been reviewed.

  Thanks for helping out!
-->

To help with the load of incoming pull requests:

- [ ] I have reviewed two other [open pull requests][prs] in this repository.

[prs]: https://github.com/home-assistant/core/pulls?q=is%3Aopen+is%3Apr+-author%3A%40me+-draft%3Atrue+-label%3Awaiting-for-upstream+sort%3Acreated-desc+review%3Anone+-status%3Afailure

<!--
  Thank you for contributing <3

  Below, some useful links you could explore:
-->
[dev-checklist]: https://developers.home-assistant.io/docs/development_checklist/
[manifest-docs]: https://developers.home-assistant.io/docs/creating_integration_manifest/
[quality-scale]: https://developers.home-assistant.io/docs/integration_quality_scale_index/
[docs-repository]: https://github.com/home-assistant/home-assistant.io
[perfect-pr]: https://developers.home-assistant.io/docs/review-process/#creating-the-perfect-pr


Problem statement:
Energy sensor value changed from Kw to W resets after restart HA.
### Checklist

- [X] I have updated to the latest available Home Assistant version.
- [X] I have cleared the cache of my browser.
- [X] I have tried a different browser to see if it is related to my browser.

### Describe the issue you are experiencing

I have a 3 phase smart meter and activated the phase entities. Like sensor.electricity_meter_power_production_phase_l1. I then changed the standard display mode from Kw to W. This works fine.
But after a reset the values are shown in Kw again and you can't change them back. I have to first give the entity a new name (save), then delete the name and only then I can change it to W again.

### Describe the behavior you expected

I expect that after a reset my chosen values remain the same and are not reset.

### Steps to reproduce the issue

1. I activated the phase entities of /dev/serial/by-id/usb-FTDI_FT232R_USB_UART_A4VTCIE8-if00-port0
DSMR Slimme Meter.
2.  Then I changed them from Kw to W. This works fine.
3. I then restarted HA
4. The values are reset to Kw again
...


### What version of Home Assistant Core has the issue?

10.1

### What was the last working version of Home Assistant Core?

_No response_

### In which browser are you experiencing the issue with?

Versie 113.0.5672.64

### Which operating system are you using to run this browser?

windows 11

### State of relevant entities

```txt
sensor.electricity_meter_power_consumption_phase_l1
Electricity Meter Power consumption phase L1
0.285	state_class: measurement
unit_of_measurement: kW
device_class: power
friendly_name: Electricity Meter Power consumption phase L1
sensor.electricity_meter_power_consumption_phase_l2
Electricity Meter Power consumption phase L2
0.024	state_class: measurement
unit_of_measurement: kW
device_class: power
friendly_name: Electricity Meter Power consumption phase L2
sensor.electricity_meter_power_consumption_phase_l3
Electricity Meter Power consumption phase L3
0.431	state_class: measurement
unit_of_measurement: kW
device_class: power
friendly_name: Electricity Meter Power consumption phase L3
sensor.electricity_meter_power_production_phase_l1
Electricity Meter Power production phase L1
0.0	state_class: measurement
unit_of_measurement: W
device_class: power
friendly_name: Electricity Meter Power production phase L1
sensor.electricity_meter_power_production_phase_l2
Electricity Meter Power production phase L2
0.0	state_class: measurement
unit_of_measurement: W
device_class: power
friendly_name: Electricity Meter Power production phase L2
sensor.electricity_meter_power_production_phase_l3
Electricity Meter Power production phase L3
0.0	state_class: measurement
unit_of_measurement: kW
device_class: power
friendly_name: Electricity Meter Power production phase L3
```


### Problem-relevant frontend configuration

_No response_

### Javascript errors shown in your browser console/inspector

_No response_

### Additional information

_No response_

Hints from issue discussion:
I have the same problem

There might be more than 1 bug here. The first bug is that the power settings reset from W to kW at last on all 4 of the power consumption sensor settings. I assume this is probably also the case for power production and energy consumption/production.

There is a second bug that in order to change the power setting back from kW to W, you additionally need to do something else (like the display precision) so that HA will save the power setting unit change.
Same problem here...
I have noticed exactly the same issue.

Hey there @robbie1221, @frenck, mind taking a look at this issue as it has been labeled with an integration (`dsmr`) you are listed as a [code owner](https://github.com/home-assistant/core/blob/dev/CODEOWNERS#L296) for? Thanks!

<details>
<summary>Code owner commands</summary>


Code owners of `dsmr` can trigger bot actions by commenting:

- `@home-assistant close` Closes the issue.
- `@home-assistant rename Awesome new title` Renames the issue.
- `@home-assistant reopen` Reopen the issue.
- `@home-assistant unassign dsmr` Removes the current integration label and assignees on the issue, add the integration domain after the command.

</details>


<sub><sup>(message by CodeOwnersMention)</sup></sub>

---

[dsmr documentation](https://www.home-assistant.io/integrations/dsmr)
[dsmr source](https://github.com/home-assistant/core/tree/dev/homeassistant/components/dsmr)
<sub><sup>(message by IssueLinks)</sup></sub>
Same problems (both) here. Maybe also an issue is that automation is also looking at this, and thus is never triggered in my case.  It would be nice that the kW or W can be added to the automation value (eg condition) 
I changed the refresh time interval of the DSMR. This resulted also in the same issue of changing back from kW to W
To verify, what integration are you using? DSMR or DSMR reader?
/config/integrations/integration/dsmr
I think that's DSMR  (DSMR Slimme Meter) (docu leads to https://www.home-assistant.io/integrations/dsmr)
I think I might know why this is happening. The sensor provides a unit of measurement. I think (I don't own any dsmr or worked with any) the unit of measurement is different per version/reader whatever. So the sensor gets the unit of measurement from the latest telegram. When the integration just started, the latest telegram isn't known, so it returns no unit of measurement. Until the moment the first telegram comes in, after which the unit of measurement is known.

I think since that moment in between where the unit of measurement is not returned, the platform is like: "I can't transform None to W or kW (or whatever) I am going to reset this choice".

There are ways to restore the unit of measurement (and the value, but I think that's not what we really want here), but I need more knowledge of the DSMR to be able to fix this.

Commit message: Supply dsmr entities jit on first telegram

Patch to review:
diff --git a/homeassistant/components/dsmr/sensor.py b/homeassistant/components/dsmr/sensor.py
index 2ff0a834e9eb56..e89b41f74e48f3 100644
--- a/homeassistant/components/dsmr/sensor.py
+++ b/homeassistant/components/dsmr/sensor.py
@@ -3,9 +3,11 @@
 
 import asyncio
 from asyncio import CancelledError
+from collections.abc import Callable
 from contextlib import suppress
 from dataclasses import dataclass
 from datetime import timedelta
+from enum import StrEnum
 from functools import partial
 
 from dsmr_parser import obis_references
@@ -34,6 +36,10 @@
 )
 from homeassistant.core import CoreState, Event, HomeAssistant, callback
 from homeassistant.helpers.device_registry import DeviceInfo
+from homeassistant.helpers.dispatcher import (
+    async_dispatcher_connect,
+    async_dispatcher_send,
+)
 from homeassistant.helpers.entity_platform import AddEntitiesCallback
 from homeassistant.helpers.typing import StateType
 from homeassistant.util import Throttle
@@ -58,6 +64,8 @@
     LOGGER,
 )
 
+EVENT_FIRST_TELEGRAM = "dsmr_first_telegram_{}"
+
 UNIT_CONVERSION = {"m3": UnitOfVolume.CUBIC_METERS}
 
 
@@ -387,17 +395,58 @@ async def async_setup_entry(
 ) -> None:
     """Set up the DSMR sensor."""
     dsmr_version = entry.data[CONF_DSMR_VERSION]
-    entities = [
-        DSMREntity(description, entry)
-        for description in SENSORS
-        if (
-            description.dsmr_versions is None
-            or dsmr_version in description.dsmr_versions
+    entities: list[DSMREntity] = []
+    initialized: bool = False
+    add_entities_handler: Callable[..., None] | None
+
+    @callback
+    def init_async_add_entities(telegram: dict[str, DSMRObject]) -> None:
+        """Add the sensor entities after the first datadram was received."""
+        nonlocal add_entities_handler
+        assert add_entities_handler is not None
+        add_entities_handler()
+        add_entities_handler = None
+
+        def device_class_and_uom(
+            telegram: dict[str, DSMRObject],
+            entity_description: DSMRSensorEntityDescription,
+        ) -> tuple[SensorDeviceClass | None, str | StrEnum | None]:
+            """Get native unit of measurement from telegram,."""
+            dsmr_object = telegram[entity_description.obis_reference]
+            uom: str | None = getattr(dsmr_object, "unit") or None
+            with suppress(ValueError):
+                if entity_description.device_class == SensorDeviceClass.GAS and (
+                    enery_uom := UnitOfEnergy(str(uom))
+                ):
+                    return (SensorDeviceClass.ENERGY, enery_uom)
+            if uom in UNIT_CONVERSION:
+                return (entity_description.device_class, UNIT_CONVERSION[uom])
+            return (entity_description.device_class, uom)
+
+        entities.extend(
+            [
+                DSMREntity(
+                    description,
+                    entry,
+                    telegram,
+                    *device_class_and_uom(
+                        telegram, description
+                    ),  # type: ignore[arg-type]
+                )
+                for description in SENSORS
+                if (
+                    description.dsmr_versions is None
+                    or dsmr_version in description.dsmr_versions
+                )
+                and (not description.is_gas or CONF_SERIAL_ID_GAS in entry.data)
+                and description.obis_reference in telegram
+            ]
         )
-        and (not description.is_gas or CONF_SERIAL_ID_GAS in entry.data)
-    ]
-    async_add_entities(entities)
+        async_add_entities(entities)
 
+    add_entities_handler = async_dispatcher_connect(
+        hass, EVENT_FIRST_TELEGRAM.format(entry.entry_id), init_async_add_entities
+    )
     min_time_between_updates = timedelta(
         seconds=entry.options.get(CONF_TIME_BETWEEN_UPDATE, DEFAULT_TIME_BETWEEN_UPDATE)
     )
@@ -405,10 +454,17 @@ async def async_setup_entry(
     @Throttle(min_time_between_updates)
     def update_entities_telegram(telegram: dict[str, DSMRObject] | None) -> None:
         """Update entities with latest telegram and trigger state update."""
+        nonlocal initialized
         # Make all device entities aware of new telegram
         for entity in entities:
             entity.update_data(telegram)
 
+        if not initialized and telegram:
+            initialized = True
+            async_dispatcher_send(
+                hass, EVENT_FIRST_TELEGRAM.format(entry.entry_id), telegram
+            )
+
     # Creates an asyncio.Protocol factory for reading DSMR telegrams from
     # serial and calls update_entities_telegram to update entities on arrival
     protocol = entry.data.get(CONF_PROTOCOL, DSMR_PROTOCOL)
@@ -525,6 +581,8 @@ def close_transport(_event: Event) -> None:
 
     @callback
     async def _async_stop(_: Event) -> None:
+        if add_entities_handler is not None:
+            add_entities_handler()
         task.cancel()
 
     # Make sure task is cancelled on shutdown (or tests complete)
@@ -544,12 +602,19 @@ class DSMREntity(SensorEntity):
     _attr_should_poll = False
 
     def __init__(
-        self, entity_description: DSMRSensorEntityDescription, entry: ConfigEntry
+        self,
+        entity_description: DSMRSensorEntityDescription,
+        entry: ConfigEntry,
+        telegram: dict[str, DSMRObject],
+        device_class: SensorDeviceClass,
+        native_unit_of_measurement: str | None,
     ) -> None:
         """Initialize entity."""
         self.entity_description = entity_description
+        self._attr_device_class = device_class
+        self._attr_native_unit_of_measurement = native_unit_of_measurement
         self._entry = entry
-        self.telegram: dict[str, DSMRObject] | None = {}
+        self.telegram: dict[str, DSMRObject] | None = telegram
 
         device_serial = entry.data[CONF_SERIAL_ID]
         device_name = DEVICE_NAME_ELECTRICITY
@@ -593,21 +658,6 @@ def available(self) -> bool:
         """Entity is only available if there is a telegram."""
         return self.telegram is not None
 
-    @property
-    def device_class(self) -> SensorDeviceClass | None:
-        """Return the device class of this entity."""
-        device_class = super().device_class
-
-        # Override device class for gas sensors providing energy units, like
-        # kWh, MWh, GJ, etc. In those cases, the class should be energy, not gas
-        with suppress(ValueError):
-            if device_class == SensorDeviceClass.GAS and UnitOfEnergy(
-                str(self.native_unit_of_measurement)
-            ):
-                return SensorDeviceClass.ENERGY
-
-        return device_class
-
     @property
     def native_value(self) -> StateType:
         """Return the state of sensor, if available, translate if needed."""
@@ -628,14 +678,6 @@ def native_value(self) -> StateType:
 
         return value
 
-    @property
-    def native_unit_of_measurement(self) -> str | None:
-        """Return the unit of measurement of this entity, if any."""
-        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:
         """Convert 2/1 to normal/low depending on DSMR version."""
diff --git a/tests/components/dsmr/test_sensor.py b/tests/components/dsmr/test_sensor.py
index 6972f0cc0cf44e..d734f0a93d57ff 100644
--- a/tests/components/dsmr/test_sensor.py
+++ b/tests/components/dsmr/test_sensor.py
@@ -23,8 +23,6 @@
     ATTR_FRIENDLY_NAME,
     ATTR_ICON,
     ATTR_UNIT_OF_MEASUREMENT,
-    STATE_UNAVAILABLE,
-    STATE_UNKNOWN,
     UnitOfEnergy,
     UnitOfPower,
     UnitOfVolume,
@@ -84,6 +82,14 @@ async def test_default_setup(hass: HomeAssistant, dsmr_connection_fixture) -> No
     await hass.config_entries.async_setup(mock_entry.entry_id)
     await hass.async_block_till_done()
 
+    telegram_callback = connection_factory.call_args_list[0][0][2]
+
+    # simulate a telegram pushed from the smartmeter and parsed by dsmr_parser
+    telegram_callback(telegram)
+
+    # after receiving telegram entities need to have the chance to be created
+    await hass.async_block_till_done()
+
     registry = er.async_get(hass)
 
     entry = registry.async_get("sensor.electricity_meter_power_consumption")
@@ -94,11 +100,9 @@ async def test_default_setup(hass: HomeAssistant, dsmr_connection_fixture) -> No
     assert entry
     assert entry.unique_id == "5678_gas_meter_reading"
 
-    telegram_callback = connection_factory.call_args_list[0][0][2]
-
-    # make sure entities have been created and return 'unavailable' state
+    # make sure entities are initialized
     power_consumption = hass.states.get("sensor.electricity_meter_power_consumption")
-    assert power_consumption.state == STATE_UNAVAILABLE
+    assert power_consumption.state == "0.0"
     assert (
         power_consumption.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.POWER
     )
@@ -107,7 +111,24 @@ async def test_default_setup(hass: HomeAssistant, dsmr_connection_fixture) -> No
         power_consumption.attributes.get(ATTR_STATE_CLASS)
         == SensorStateClass.MEASUREMENT
     )
-    assert power_consumption.attributes.get(ATTR_UNIT_OF_MEASUREMENT) is None
+    assert power_consumption.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == "W"
+
+    telegram = {
+        CURRENT_ELECTRICITY_USAGE: CosemObject(
+            CURRENT_ELECTRICITY_USAGE,
+            [{"value": Decimal("35.0"), "unit": UnitOfPower.WATT}],
+        ),
+        ELECTRICITY_ACTIVE_TARIFF: CosemObject(
+            ELECTRICITY_ACTIVE_TARIFF, [{"value": "0001", "unit": ""}]
+        ),
+        GAS_METER_READING: MBusObject(
+            GAS_METER_READING,
+            [
+                {"value": datetime.datetime.fromtimestamp(1551642214)},
+                {"value": Decimal(745.701), "unit": UnitOfVolume.CUBIC_METERS},
+            ],
+        ),
+    }
 
     # simulate a telegram pushed from the smartmeter and parsed by dsmr_parser
     telegram_callback(telegram)
@@ -117,7 +138,7 @@ async def test_default_setup(hass: HomeAssistant, dsmr_connection_fixture) -> No
 
     # ensure entities have new state value after incoming telegram
     power_consumption = hass.states.get("sensor.electricity_meter_power_consumption")
-    assert power_consumption.state == "0.0"
+    assert power_consumption.state == "35.0"
     assert power_consumption.attributes.get("unit_of_measurement") == UnitOfPower.WATT
 
     # tariff should be translated in human readable and have no unit
@@ -131,11 +152,11 @@ async def test_default_setup(hass: HomeAssistant, dsmr_connection_fixture) -> No
     )
     assert active_tariff.attributes.get(ATTR_OPTIONS) == ["low", "normal"]
     assert active_tariff.attributes.get(ATTR_STATE_CLASS) is None
-    assert active_tariff.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == ""
+    assert active_tariff.attributes.get(ATTR_UNIT_OF_MEASUREMENT) is None
 
     # check if gas consumption is parsed correctly
     gas_consumption = hass.states.get("sensor.gas_meter_gas_consumption")
-    assert gas_consumption.state == "745.695"
+    assert gas_consumption.state == "745.701"
     assert gas_consumption.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.GAS
     assert (
         gas_consumption.attributes.get(ATTR_FRIENDLY_NAME)
@@ -153,6 +174,14 @@ async def test_default_setup(hass: HomeAssistant, dsmr_connection_fixture) -> No
 
 async def test_setup_only_energy(hass: HomeAssistant, dsmr_connection_fixture) -> None:
     """Test the default setup."""
+    (connection_factory, transport, protocol) = dsmr_connection_fixture
+
+    from dsmr_parser.obis_references import (
+        CURRENT_ELECTRICITY_USAGE,
+        ELECTRICITY_ACTIVE_TARIFF,
+    )
+    from dsmr_parser.objects import CosemObject
+
     entry_data = {
         "port": "/dev/ttyUSB0",
         "dsmr_version": "2.2",
@@ -160,9 +189,22 @@ async def test_setup_only_energy(hass: HomeAssistant, dsmr_connection_fixture) -
         "reconnect_interval": 30,
         "serial_id": "1234",
     }
+    entry_options = {
+        "time_between_update": 0,
+    }
+
+    telegram = {
+        CURRENT_ELECTRICITY_USAGE: CosemObject(
+            CURRENT_ELECTRICITY_USAGE,
+            [{"value": Decimal("35.0"), "unit": UnitOfPower.WATT}],
+        ),
+        ELECTRICITY_ACTIVE_TARIFF: CosemObject(
+            ELECTRICITY_ACTIVE_TARIFF, [{"value": "0001", "unit": ""}]
+        ),
+    }
 
     mock_entry = MockConfigEntry(
-        domain="dsmr", unique_id="/dev/ttyUSB0", data=entry_data
+        domain="dsmr", unique_id="/dev/ttyUSB0", data=entry_data, options=entry_options
     )
 
     mock_entry.add_to_hass(hass)
@@ -170,6 +212,14 @@ async def test_setup_only_energy(hass: HomeAssistant, dsmr_connection_fixture) -
     await hass.config_entries.async_setup(mock_entry.entry_id)
     await hass.async_block_till_done()
 
+    telegram_callback = connection_factory.call_args_list[0][0][2]
+
+    # simulate a telegram pushed from the smartmeter and parsed by dsmr_parser
+    telegram_callback(telegram)
+
+    # after receiving telegram entities need to have the chance to be created
+    await hass.async_block_till_done()
+
     registry = er.async_get(hass)
 
     entry = registry.async_get("sensor.electricity_meter_power_consumption")
@@ -229,8 +279,8 @@ async def test_v4_meter(hass: HomeAssistant, dsmr_connection_fixture) -> None:
     # simulate a telegram pushed from the smartmeter and parsed by dsmr_parser
     telegram_callback(telegram)
 
-    # after receiving telegram entities need to have the chance to update
-    await asyncio.sleep(0)
+    # after receiving telegram entities need to have the chance to be created
+    await hass.async_block_till_done()
 
     # tariff should be translated in human readable and have no unit
     active_tariff = hass.states.get("sensor.electricity_meter_active_tariff")
@@ -239,7 +289,7 @@ async def test_v4_meter(hass: HomeAssistant, dsmr_connection_fixture) -> None:
     assert active_tariff.attributes.get(ATTR_ICON) == "mdi:flash"
     assert active_tariff.attributes.get(ATTR_OPTIONS) == ["low", "normal"]
     assert active_tariff.attributes.get(ATTR_STATE_CLASS) is None
-    assert active_tariff.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == ""
+    assert active_tariff.attributes.get(ATTR_UNIT_OF_MEASUREMENT) is None
 
     # check if gas consumption is parsed correctly
     gas_consumption = hass.states.get("sensor.gas_meter_gas_consumption")
@@ -308,8 +358,8 @@ async def test_v5_meter(hass: HomeAssistant, dsmr_connection_fixture) -> None:
     # simulate a telegram pushed from the smartmeter and parsed by dsmr_parser
     telegram_callback(telegram)
 
-    # after receiving telegram entities need to have the chance to update
-    await asyncio.sleep(0)
+    # after receiving telegram entities need to have the chance to be created
+    await hass.async_block_till_done()
 
     # tariff should be translated in human readable and have no unit
     active_tariff = hass.states.get("sensor.electricity_meter_active_tariff")
@@ -318,7 +368,7 @@ async def test_v5_meter(hass: HomeAssistant, dsmr_connection_fixture) -> None:
     assert active_tariff.attributes.get(ATTR_ICON) == "mdi:flash"
     assert active_tariff.attributes.get(ATTR_OPTIONS) == ["low", "normal"]
     assert active_tariff.attributes.get(ATTR_STATE_CLASS) is None
-    assert active_tariff.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == ""
+    assert active_tariff.attributes.get(ATTR_UNIT_OF_MEASUREMENT) is None
 
     # check if gas consumption is parsed correctly
     gas_consumption = hass.states.get("sensor.gas_meter_gas_consumption")
@@ -389,8 +439,8 @@ async def test_luxembourg_meter(hass: HomeAssistant, dsmr_connection_fixture) ->
     # simulate a telegram pushed from the smartmeter and parsed by dsmr_parser
     telegram_callback(telegram)
 
-    # after receiving telegram entities need to have the chance to update
-    await asyncio.sleep(0)
+    # after receiving telegram entities need to have the chance to be created
+    await hass.async_block_till_done()
 
     active_tariff = hass.states.get("sensor.electricity_meter_energy_consumption_total")
     assert active_tariff.state == "123.456"
@@ -472,8 +522,8 @@ async def test_belgian_meter(hass: HomeAssistant, dsmr_connection_fixture) -> No
     # simulate a telegram pushed from the smartmeter and parsed by dsmr_parser
     telegram_callback(telegram)
 
-    # after receiving telegram entities need to have the chance to update
-    await asyncio.sleep(0)
+    # after receiving telegram entities need to have the chance to be created
+    await hass.async_block_till_done()
 
     # tariff should be translated in human readable and have no unit
     active_tariff = hass.states.get("sensor.electricity_meter_active_tariff")
@@ -482,7 +532,7 @@ async def test_belgian_meter(hass: HomeAssistant, dsmr_connection_fixture) -> No
     assert active_tariff.attributes.get(ATTR_ICON) == "mdi:flash"
     assert active_tariff.attributes.get(ATTR_OPTIONS) == ["low", "normal"]
     assert active_tariff.attributes.get(ATTR_STATE_CLASS) is None
-    assert active_tariff.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == ""
+    assert active_tariff.attributes.get(ATTR_UNIT_OF_MEASUREMENT) is None
 
     # check if gas consumption is parsed correctly
     gas_consumption = hass.states.get("sensor.gas_meter_gas_consumption")
@@ -537,8 +587,8 @@ async def test_belgian_meter_low(hass: HomeAssistant, dsmr_connection_fixture) -
     # simulate a telegram pushed from the smartmeter and parsed by dsmr_parser
     telegram_callback(telegram)
 
-    # after receiving telegram entities need to have the chance to update
-    await asyncio.sleep(0)
+    # after receiving telegram entities need to have the chance to be created
+    await hass.async_block_till_done()
 
     # tariff should be translated in human readable and have no unit
     active_tariff = hass.states.get("sensor.electricity_meter_active_tariff")
@@ -547,7 +597,7 @@ async def test_belgian_meter_low(hass: HomeAssistant, dsmr_connection_fixture) -
     assert active_tariff.attributes.get(ATTR_ICON) == "mdi:flash"
     assert active_tariff.attributes.get(ATTR_OPTIONS) == ["low", "normal"]
     assert active_tariff.attributes.get(ATTR_STATE_CLASS) is None
-    assert active_tariff.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == ""
+    assert active_tariff.attributes.get(ATTR_UNIT_OF_MEASUREMENT) is None
 
 
 async def test_swedish_meter(hass: HomeAssistant, dsmr_connection_fixture) -> None:
@@ -597,8 +647,8 @@ async def test_swedish_meter(hass: HomeAssistant, dsmr_connection_fixture) -> No
     # simulate a telegram pushed from the smartmeter and parsed by dsmr_parser
     telegram_callback(telegram)
 
-    # after receiving telegram entities need to have the chance to update
-    await asyncio.sleep(0)
+    # after receiving telegram entities need to have the chance to be created
+    await hass.async_block_till_done()
 
     active_tariff = hass.states.get("sensor.electricity_meter_energy_consumption_total")
     assert active_tariff.state == "123.456"
@@ -675,8 +725,8 @@ async def test_easymeter(hass: HomeAssistant, dsmr_connection_fixture) -> None:
     # simulate a telegram pushed from the smartmeter and parsed by dsmr_parser
     telegram_callback(telegram)
 
-    # after receiving telegram entities need to have the chance to update
-    await asyncio.sleep(0)
+    # after receiving telegram entities need to have the chance to be created
+    await hass.async_block_till_done()
 
     active_tariff = hass.states.get("sensor.electricity_meter_energy_consumption_total")
     assert active_tariff.state == "54184.6316"
@@ -800,6 +850,12 @@ async def test_connection_errors_retry(
 
 async def test_reconnect(hass: HomeAssistant, dsmr_connection_fixture) -> None:
     """If transport disconnects, the connection should be retried."""
+    from dsmr_parser.obis_references import (
+        CURRENT_ELECTRICITY_USAGE,
+        ELECTRICITY_ACTIVE_TARIFF,
+    )
+    from dsmr_parser.objects import CosemObject
+
     (connection_factory, transport, protocol) = dsmr_connection_fixture
 
     entry_data = {
@@ -810,6 +866,19 @@ async def test_reconnect(hass: HomeAssistant, dsmr_connection_fixture) -> None:
         "serial_id": "1234",
         "serial_id_gas": "5678",
     }
+    entry_options = {
+        "time_between_update": 0,
+    }
+
+    telegram = {
+        CURRENT_ELECTRICITY_USAGE: CosemObject(
+            CURRENT_ELECTRICITY_USAGE,
+            [{"value": Decimal("35.0"), "unit": UnitOfPower.WATT}],
+        ),
+        ELECTRICITY_ACTIVE_TARIFF: CosemObject(
+            ELECTRICITY_ACTIVE_TARIFF, [{"value": "0001", "unit": ""}]
+        ),
+    }
 
     # mock waiting coroutine while connection lasts
     closed = asyncio.Event()
@@ -823,7 +892,7 @@ async def wait_closed():
     protocol.wait_closed = wait_closed
 
     mock_entry = MockConfigEntry(
-        domain="dsmr", unique_id="/dev/ttyUSB0", data=entry_data
+        domain="dsmr", unique_id="/dev/ttyUSB0", data=entry_data, options=entry_options
     )
 
     mock_entry.add_to_hass(hass)
@@ -831,11 +900,19 @@ async def wait_closed():
     await hass.config_entries.async_setup(mock_entry.entry_id)
     await hass.async_block_till_done()
 
+    telegram_callback = connection_factory.call_args_list[0][0][2]
+
+    # simulate a telegram pushed from the smartmeter and parsed by dsmr_parser
+    telegram_callback(telegram)
+
+    # after receiving telegram entities need to have the chance to be created
+    await hass.async_block_till_done()
+
     assert connection_factory.call_count == 1
 
     state = hass.states.get("sensor.electricity_meter_power_consumption")
     assert state
-    assert state.state == STATE_UNKNOWN
+    assert state.state == "35.0"
 
     # indicate disconnect, release wait lock and allow reconnect to happen
     closed.set()
@@ -897,7 +974,7 @@ async def test_gas_meter_providing_energy_reading(
 
     telegram_callback = connection_factory.call_args_list[0][0][2]
     telegram_callback(telegram)
-    await asyncio.sleep(0)
+    await hass.async_block_till_done()
 
     gas_consumption = hass.states.get("sensor.gas_meter_gas_consumption")
     assert gas_consumption.state == "123.456"


Code review comments:
diff_hunk: @@ -387,28 +395,76 @@ async def async_setup_entry(
 ) -> None:
     """Set up the DSMR sensor."""
     dsmr_version = entry.data[CONF_DSMR_VERSION]
-    entities = [
-        DSMREntity(description, entry)
-        for description in SENSORS
-        if (
-            description.dsmr_versions is None
-            or dsmr_version in description.dsmr_versions
+    entities: list[DSMREntity] = []
+    initialized: bool = False
+    add_entities_handler: Callable[..., None] | None
+
+    @callback
+    def init_async_add_entities(telegram: dict[str, DSMRObject]) -> None:
+        """Add the sensor entities after the first datadram was received."""
+        nonlocal add_entities_handler
+        assert add_entities_handler is not None
+        add_entities_handler()
+        add_entities_handler = None
+
+        def device_class_and_uom(
+            telegram: dict[str, DSMRObject],
+            entity_description: DSMRSensorEntityDescription,
+        ) -> tuple[SensorDeviceClass | None, str | StrEnum | None]:
path: homeassistant/components/dsmr/sensor.py
text: @user1:
```suggestion
        ) -> tuple[SensorDeviceClass | None, str | None]:
```
Doesn't this always return str?

@author:
No , but I'll have a look at mypy

diff_hunk: @@ -387,28 +395,76 @@ async def async_setup_entry(
 ) -> None:
     """Set up the DSMR sensor."""
     dsmr_version = entry.data[CONF_DSMR_VERSION]
-    entities = [
-        DSMREntity(description, entry)
-        for description in SENSORS
-        if (
-            description.dsmr_versions is None
-            or dsmr_version in description.dsmr_versions
+    entities: list[DSMREntity] = []
+    initialized: bool = False
+    add_entities_handler: Callable[..., None] | None
+
+    @callback
+    def init_async_add_entities(telegram: dict[str, DSMRObject]) -> None:
+        """Add the sensor entities after the first datadram was received."""
path: homeassistant/components/dsmr/sensor.py
text: @user1:
```suggestion
        """Add the sensor entities after the first telegram was received."""
```