mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 05:07:41 +00:00
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:
parent
c574cefc30
commit
9db9f1b8a9
@ -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
|
||||||
for description in SENSORS
|
add_entities_handler: Callable[..., None] | None
|
||||||
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)
|
|
||||||
]
|
|
||||||
async_add_entities(entities)
|
|
||||||
|
|
||||||
|
@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
|
||||||
|
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
|
||||||
|
]
|
||||||
|
)
|
||||||
|
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."""
|
||||||
|
@ -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"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user