Fix suggested UOM cannot be set for dsmr entities (#102134)

* Supply dsmr entities jit on first telegram

* Stale docstr

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>

* Simplify tuple type

---------

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
This commit is contained in:
Jan Bouwhuis 2023-10-19 19:22:03 +02:00 committed by GitHub
parent c574cefc30
commit 9db9f1b8a9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 185 additions and 67 deletions

View File

@ -3,6 +3,7 @@ from __future__ import annotations
import asyncio import asyncio
from asyncio import CancelledError from asyncio import CancelledError
from collections.abc import Callable
from contextlib import suppress from contextlib import suppress
from dataclasses import dataclass from dataclasses import dataclass
from datetime import timedelta from datetime import timedelta
@ -34,6 +35,10 @@ from homeassistant.const import (
) )
from homeassistant.core import CoreState, Event, HomeAssistant, callback from homeassistant.core import CoreState, Event, HomeAssistant, callback
from homeassistant.helpers.device_registry import DeviceInfo 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.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType from homeassistant.helpers.typing import StateType
from homeassistant.util import Throttle from homeassistant.util import Throttle
@ -58,6 +63,8 @@ from .const import (
LOGGER, LOGGER,
) )
EVENT_FIRST_TELEGRAM = "dsmr_first_telegram_{}"
UNIT_CONVERSION = {"m3": UnitOfVolume.CUBIC_METERS} UNIT_CONVERSION = {"m3": UnitOfVolume.CUBIC_METERS}
@ -387,17 +394,58 @@ async def async_setup_entry(
) -> None: ) -> None:
"""Set up the DSMR sensor.""" """Set up the DSMR sensor."""
dsmr_version = entry.data[CONF_DSMR_VERSION] dsmr_version = entry.data[CONF_DSMR_VERSION]
entities = [ entities: list[DSMREntity] = []
DSMREntity(description, entry) 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 telegram 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 | 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 for description in SENSORS
if ( if (
description.dsmr_versions is None description.dsmr_versions is None
or dsmr_version in description.dsmr_versions or dsmr_version in description.dsmr_versions
) )
and (not description.is_gas or CONF_SERIAL_ID_GAS in entry.data) and (not description.is_gas or CONF_SERIAL_ID_GAS in entry.data)
and description.obis_reference in telegram
] ]
)
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( min_time_between_updates = timedelta(
seconds=entry.options.get(CONF_TIME_BETWEEN_UPDATE, DEFAULT_TIME_BETWEEN_UPDATE) seconds=entry.options.get(CONF_TIME_BETWEEN_UPDATE, DEFAULT_TIME_BETWEEN_UPDATE)
) )
@ -405,10 +453,17 @@ async def async_setup_entry(
@Throttle(min_time_between_updates) @Throttle(min_time_between_updates)
def update_entities_telegram(telegram: dict[str, DSMRObject] | None) -> None: def update_entities_telegram(telegram: dict[str, DSMRObject] | None) -> None:
"""Update entities with latest telegram and trigger state update.""" """Update entities with latest telegram and trigger state update."""
nonlocal initialized
# Make all device entities aware of new telegram # Make all device entities aware of new telegram
for entity in entities: for entity in entities:
entity.update_data(telegram) 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 # Creates an asyncio.Protocol factory for reading DSMR telegrams from
# serial and calls update_entities_telegram to update entities on arrival # serial and calls update_entities_telegram to update entities on arrival
protocol = entry.data.get(CONF_PROTOCOL, DSMR_PROTOCOL) protocol = entry.data.get(CONF_PROTOCOL, DSMR_PROTOCOL)
@ -525,6 +580,8 @@ async def async_setup_entry(
@callback @callback
async def _async_stop(_: Event) -> None: async def _async_stop(_: Event) -> None:
if add_entities_handler is not None:
add_entities_handler()
task.cancel() task.cancel()
# Make sure task is cancelled on shutdown (or tests complete) # Make sure task is cancelled on shutdown (or tests complete)
@ -544,12 +601,19 @@ class DSMREntity(SensorEntity):
_attr_should_poll = False _attr_should_poll = False
def __init__( 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: ) -> None:
"""Initialize entity.""" """Initialize entity."""
self.entity_description = entity_description 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._entry = entry
self.telegram: dict[str, DSMRObject] | None = {} self.telegram: dict[str, DSMRObject] | None = telegram
device_serial = entry.data[CONF_SERIAL_ID] device_serial = entry.data[CONF_SERIAL_ID]
device_name = DEVICE_NAME_ELECTRICITY device_name = DEVICE_NAME_ELECTRICITY
@ -593,21 +657,6 @@ class DSMREntity(SensorEntity):
"""Entity is only available if there is a telegram.""" """Entity is only available if there is a telegram."""
return self.telegram is not None 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 @property
def native_value(self) -> StateType: def native_value(self) -> StateType:
"""Return the state of sensor, if available, translate if needed.""" """Return the state of sensor, if available, translate if needed."""
@ -628,14 +677,6 @@ class DSMREntity(SensorEntity):
return value 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 @staticmethod
def translate_tariff(value: str, dsmr_version: str) -> str | None: def translate_tariff(value: str, dsmr_version: str) -> str | None:
"""Convert 2/1 to normal/low depending on DSMR version.""" """Convert 2/1 to normal/low depending on DSMR version."""

View File

@ -23,8 +23,6 @@ from homeassistant.const import (
ATTR_FRIENDLY_NAME, ATTR_FRIENDLY_NAME,
ATTR_ICON, ATTR_ICON,
ATTR_UNIT_OF_MEASUREMENT, ATTR_UNIT_OF_MEASUREMENT,
STATE_UNAVAILABLE,
STATE_UNKNOWN,
UnitOfEnergy, UnitOfEnergy,
UnitOfPower, UnitOfPower,
UnitOfVolume, 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.config_entries.async_setup(mock_entry.entry_id)
await hass.async_block_till_done() 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) registry = er.async_get(hass)
entry = registry.async_get("sensor.electricity_meter_power_consumption") 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
assert entry.unique_id == "5678_gas_meter_reading" assert entry.unique_id == "5678_gas_meter_reading"
telegram_callback = connection_factory.call_args_list[0][0][2] # make sure entities are initialized
# make sure entities have been created and return 'unavailable' state
power_consumption = hass.states.get("sensor.electricity_meter_power_consumption") power_consumption = hass.states.get("sensor.electricity_meter_power_consumption")
assert power_consumption.state == STATE_UNAVAILABLE assert power_consumption.state == "0.0"
assert ( assert (
power_consumption.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.POWER 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) power_consumption.attributes.get(ATTR_STATE_CLASS)
== SensorStateClass.MEASUREMENT == 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 # simulate a telegram pushed from the smartmeter and parsed by dsmr_parser
telegram_callback(telegram) 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 # ensure entities have new state value after incoming telegram
power_consumption = hass.states.get("sensor.electricity_meter_power_consumption") 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 assert power_consumption.attributes.get("unit_of_measurement") == UnitOfPower.WATT
# tariff should be translated in human readable and have no unit # 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_OPTIONS) == ["low", "normal"]
assert active_tariff.attributes.get(ATTR_STATE_CLASS) is None 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 # check if gas consumption is parsed correctly
gas_consumption = hass.states.get("sensor.gas_meter_gas_consumption") 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_DEVICE_CLASS) == SensorDeviceClass.GAS
assert ( assert (
gas_consumption.attributes.get(ATTR_FRIENDLY_NAME) 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: async def test_setup_only_energy(hass: HomeAssistant, dsmr_connection_fixture) -> None:
"""Test the default setup.""" """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 = { entry_data = {
"port": "/dev/ttyUSB0", "port": "/dev/ttyUSB0",
"dsmr_version": "2.2", "dsmr_version": "2.2",
@ -160,9 +189,22 @@ async def test_setup_only_energy(hass: HomeAssistant, dsmr_connection_fixture) -
"reconnect_interval": 30, "reconnect_interval": 30,
"serial_id": "1234", "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( 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) 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.config_entries.async_setup(mock_entry.entry_id)
await hass.async_block_till_done() 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) registry = er.async_get(hass)
entry = registry.async_get("sensor.electricity_meter_power_consumption") 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 # simulate a telegram pushed from the smartmeter and parsed by dsmr_parser
telegram_callback(telegram) telegram_callback(telegram)
# after receiving telegram entities need to have the chance to update # after receiving telegram entities need to have the chance to be created
await asyncio.sleep(0) await hass.async_block_till_done()
# tariff should be translated in human readable and have no unit # tariff should be translated in human readable and have no unit
active_tariff = hass.states.get("sensor.electricity_meter_active_tariff") 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_ICON) == "mdi:flash"
assert active_tariff.attributes.get(ATTR_OPTIONS) == ["low", "normal"] 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_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 # check if gas consumption is parsed correctly
gas_consumption = hass.states.get("sensor.gas_meter_gas_consumption") 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 # simulate a telegram pushed from the smartmeter and parsed by dsmr_parser
telegram_callback(telegram) telegram_callback(telegram)
# after receiving telegram entities need to have the chance to update # after receiving telegram entities need to have the chance to be created
await asyncio.sleep(0) await hass.async_block_till_done()
# tariff should be translated in human readable and have no unit # tariff should be translated in human readable and have no unit
active_tariff = hass.states.get("sensor.electricity_meter_active_tariff") 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_ICON) == "mdi:flash"
assert active_tariff.attributes.get(ATTR_OPTIONS) == ["low", "normal"] 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_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 # check if gas consumption is parsed correctly
gas_consumption = hass.states.get("sensor.gas_meter_gas_consumption") 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 # simulate a telegram pushed from the smartmeter and parsed by dsmr_parser
telegram_callback(telegram) telegram_callback(telegram)
# after receiving telegram entities need to have the chance to update # after receiving telegram entities need to have the chance to be created
await asyncio.sleep(0) await hass.async_block_till_done()
active_tariff = hass.states.get("sensor.electricity_meter_energy_consumption_total") active_tariff = hass.states.get("sensor.electricity_meter_energy_consumption_total")
assert active_tariff.state == "123.456" 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 # simulate a telegram pushed from the smartmeter and parsed by dsmr_parser
telegram_callback(telegram) telegram_callback(telegram)
# after receiving telegram entities need to have the chance to update # after receiving telegram entities need to have the chance to be created
await asyncio.sleep(0) await hass.async_block_till_done()
# tariff should be translated in human readable and have no unit # tariff should be translated in human readable and have no unit
active_tariff = hass.states.get("sensor.electricity_meter_active_tariff") 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_ICON) == "mdi:flash"
assert active_tariff.attributes.get(ATTR_OPTIONS) == ["low", "normal"] 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_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 # check if gas consumption is parsed correctly
gas_consumption = hass.states.get("sensor.gas_meter_gas_consumption") 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 # simulate a telegram pushed from the smartmeter and parsed by dsmr_parser
telegram_callback(telegram) telegram_callback(telegram)
# after receiving telegram entities need to have the chance to update # after receiving telegram entities need to have the chance to be created
await asyncio.sleep(0) await hass.async_block_till_done()
# tariff should be translated in human readable and have no unit # tariff should be translated in human readable and have no unit
active_tariff = hass.states.get("sensor.electricity_meter_active_tariff") 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_ICON) == "mdi:flash"
assert active_tariff.attributes.get(ATTR_OPTIONS) == ["low", "normal"] 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_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: 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 # simulate a telegram pushed from the smartmeter and parsed by dsmr_parser
telegram_callback(telegram) telegram_callback(telegram)
# after receiving telegram entities need to have the chance to update # after receiving telegram entities need to have the chance to be created
await asyncio.sleep(0) await hass.async_block_till_done()
active_tariff = hass.states.get("sensor.electricity_meter_energy_consumption_total") active_tariff = hass.states.get("sensor.electricity_meter_energy_consumption_total")
assert active_tariff.state == "123.456" 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 # simulate a telegram pushed from the smartmeter and parsed by dsmr_parser
telegram_callback(telegram) telegram_callback(telegram)
# after receiving telegram entities need to have the chance to update # after receiving telegram entities need to have the chance to be created
await asyncio.sleep(0) await hass.async_block_till_done()
active_tariff = hass.states.get("sensor.electricity_meter_energy_consumption_total") active_tariff = hass.states.get("sensor.electricity_meter_energy_consumption_total")
assert active_tariff.state == "54184.6316" 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: async def test_reconnect(hass: HomeAssistant, dsmr_connection_fixture) -> None:
"""If transport disconnects, the connection should be retried.""" """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 (connection_factory, transport, protocol) = dsmr_connection_fixture
entry_data = { entry_data = {
@ -810,6 +866,19 @@ async def test_reconnect(hass: HomeAssistant, dsmr_connection_fixture) -> None:
"serial_id": "1234", "serial_id": "1234",
"serial_id_gas": "5678", "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 # mock waiting coroutine while connection lasts
closed = asyncio.Event() closed = asyncio.Event()
@ -823,7 +892,7 @@ async def test_reconnect(hass: HomeAssistant, dsmr_connection_fixture) -> None:
protocol.wait_closed = wait_closed protocol.wait_closed = wait_closed
mock_entry = MockConfigEntry( 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) mock_entry.add_to_hass(hass)
@ -831,11 +900,19 @@ async def test_reconnect(hass: HomeAssistant, dsmr_connection_fixture) -> None:
await hass.config_entries.async_setup(mock_entry.entry_id) await hass.config_entries.async_setup(mock_entry.entry_id)
await hass.async_block_till_done() 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 assert connection_factory.call_count == 1
state = hass.states.get("sensor.electricity_meter_power_consumption") state = hass.states.get("sensor.electricity_meter_power_consumption")
assert state assert state
assert state.state == STATE_UNKNOWN assert state.state == "35.0"
# indicate disconnect, release wait lock and allow reconnect to happen # indicate disconnect, release wait lock and allow reconnect to happen
closed.set() 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 = connection_factory.call_args_list[0][0][2]
telegram_callback(telegram) telegram_callback(telegram)
await asyncio.sleep(0) await hass.async_block_till_done()
gas_consumption = hass.states.get("sensor.gas_meter_gas_consumption") gas_consumption = hass.states.get("sensor.gas_meter_gas_consumption")
assert gas_consumption.state == "123.456" assert gas_consumption.state == "123.456"