Add unique_id and device_info to DSMR entities (#42279)
* Add unique ids and device info * Fix tests * Add tests * Apply suggestions from code review Co-authored-by: Chris Talkington <chris@talkingtontech.com> * Fix black Co-authored-by: Chris Talkington <chris@talkingtontech.com>
This commit is contained in:
parent
6efb782871
commit
79fac17c28
3 changed files with 110 additions and 14 deletions
|
@ -18,6 +18,9 @@ DEFAULT_RECONNECT_INTERVAL = 30
|
|||
|
||||
DATA_TASK = "task"
|
||||
|
||||
DEVICE_NAME_ENERGY = "Energy Meter"
|
||||
DEVICE_NAME_GAS = "Gas Meter"
|
||||
|
||||
ICON_GAS = "mdi:fire"
|
||||
ICON_POWER = "mdi:flash"
|
||||
ICON_POWER_FAILURE = "mdi:flash-off"
|
||||
|
|
|
@ -3,6 +3,7 @@ import asyncio
|
|||
from asyncio import CancelledError
|
||||
from functools import partial
|
||||
import logging
|
||||
from typing import Dict
|
||||
|
||||
from dsmr_parser import obis_references as obis_ref
|
||||
from dsmr_parser.clients.protocol import create_dsmr_reader, create_tcp_dsmr_reader
|
||||
|
@ -26,11 +27,15 @@ from .const import (
|
|||
CONF_DSMR_VERSION,
|
||||
CONF_PRECISION,
|
||||
CONF_RECONNECT_INTERVAL,
|
||||
CONF_SERIAL_ID,
|
||||
CONF_SERIAL_ID_GAS,
|
||||
DATA_TASK,
|
||||
DEFAULT_DSMR_VERSION,
|
||||
DEFAULT_PORT,
|
||||
DEFAULT_PRECISION,
|
||||
DEFAULT_RECONNECT_INTERVAL,
|
||||
DEVICE_NAME_ENERGY,
|
||||
DEVICE_NAME_GAS,
|
||||
DOMAIN,
|
||||
ICON_GAS,
|
||||
ICON_POWER,
|
||||
|
@ -106,21 +111,37 @@ async def async_setup_entry(
|
|||
]
|
||||
|
||||
# Generate device entities
|
||||
devices = [DSMREntity(name, obis, config) for name, obis in obis_mapping]
|
||||
devices = [
|
||||
DSMREntity(name, DEVICE_NAME_ENERGY, config[CONF_SERIAL_ID], obis, config)
|
||||
for name, obis in obis_mapping
|
||||
]
|
||||
|
||||
# Protocol version specific obis
|
||||
if dsmr_version in ("4", "5"):
|
||||
gas_obis = obis_ref.HOURLY_GAS_METER_READING
|
||||
elif dsmr_version in ("5B",):
|
||||
gas_obis = obis_ref.BELGIUM_HOURLY_GAS_METER_READING
|
||||
else:
|
||||
gas_obis = obis_ref.GAS_METER_READING
|
||||
if CONF_SERIAL_ID_GAS in config:
|
||||
if dsmr_version in ("4", "5"):
|
||||
gas_obis = obis_ref.HOURLY_GAS_METER_READING
|
||||
elif dsmr_version in ("5B",):
|
||||
gas_obis = obis_ref.BELGIUM_HOURLY_GAS_METER_READING
|
||||
else:
|
||||
gas_obis = obis_ref.GAS_METER_READING
|
||||
|
||||
# Add gas meter reading and derivative for usage
|
||||
devices += [
|
||||
DSMREntity("Gas Consumption", gas_obis, config),
|
||||
DerivativeDSMREntity("Hourly Gas Consumption", gas_obis, config),
|
||||
]
|
||||
# Add gas meter reading and derivative for usage
|
||||
devices += [
|
||||
DSMREntity(
|
||||
"Gas Consumption",
|
||||
DEVICE_NAME_GAS,
|
||||
config[CONF_SERIAL_ID_GAS],
|
||||
gas_obis,
|
||||
config,
|
||||
),
|
||||
DerivativeDSMREntity(
|
||||
"Hourly Gas Consumption",
|
||||
DEVICE_NAME_GAS,
|
||||
config[CONF_SERIAL_ID_GAS],
|
||||
gas_obis,
|
||||
config,
|
||||
),
|
||||
]
|
||||
|
||||
async_add_entities(devices)
|
||||
|
||||
|
@ -209,13 +230,17 @@ async def async_setup_entry(
|
|||
class DSMREntity(Entity):
|
||||
"""Entity reading values from DSMR telegram."""
|
||||
|
||||
def __init__(self, name, obis, config):
|
||||
def __init__(self, name, device_name, device_serial, obis, config):
|
||||
"""Initialize entity."""
|
||||
self._name = name
|
||||
self._obis = obis
|
||||
self._config = config
|
||||
self.telegram = {}
|
||||
|
||||
self._device_name = device_name
|
||||
self._device_serial = device_serial
|
||||
self._unique_id = f"{device_serial}_{name}".replace(" ", "_")
|
||||
|
||||
@callback
|
||||
def update_data(self, telegram):
|
||||
"""Update data."""
|
||||
|
@ -273,6 +298,19 @@ class DSMREntity(Entity):
|
|||
"""Return the unit of measurement of this entity, if any."""
|
||||
return self.get_dsmr_object_attr("unit")
|
||||
|
||||
@property
|
||||
def unique_id(self) -> str:
|
||||
"""Return a unique ID."""
|
||||
return self._unique_id
|
||||
|
||||
@property
|
||||
def device_info(self) -> Dict[str, any]:
|
||||
"""Return the device information."""
|
||||
return {
|
||||
"identifiers": {(DOMAIN, self._device_serial)},
|
||||
"name": self._device_name,
|
||||
}
|
||||
|
||||
@property
|
||||
def force_update(self):
|
||||
"""Force update."""
|
||||
|
|
|
@ -74,6 +74,8 @@ async def test_default_setup(hass, dsmr_connection_fixture):
|
|||
"dsmr_version": "2.2",
|
||||
"precision": 4,
|
||||
"reconnect_interval": 30,
|
||||
"serial_id": "1234",
|
||||
"serial_id_gas": "5678",
|
||||
}
|
||||
|
||||
telegram = {
|
||||
|
@ -98,6 +100,16 @@ async def test_default_setup(hass, dsmr_connection_fixture):
|
|||
await hass.config_entries.async_setup(mock_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
registry = await hass.helpers.entity_registry.async_get_registry()
|
||||
|
||||
entry = registry.async_get("sensor.power_consumption")
|
||||
assert entry
|
||||
assert entry.unique_id == "1234_Power_Consumption"
|
||||
|
||||
entry = registry.async_get("sensor.gas_consumption")
|
||||
assert entry
|
||||
assert entry.unique_id == "5678_Gas_Consumption"
|
||||
|
||||
telegram_callback = connection_factory.call_args_list[0][0][2]
|
||||
|
||||
# make sure entities have been created and return 'unknown' state
|
||||
|
@ -129,13 +141,42 @@ async def test_default_setup(hass, dsmr_connection_fixture):
|
|||
assert gas_consumption.attributes.get("unit_of_measurement") == VOLUME_CUBIC_METERS
|
||||
|
||||
|
||||
async def test_setup_only_energy(hass, dsmr_connection_fixture):
|
||||
"""Test the default setup."""
|
||||
entry_data = {
|
||||
"port": "/dev/ttyUSB0",
|
||||
"dsmr_version": "2.2",
|
||||
"precision": 4,
|
||||
"reconnect_interval": 30,
|
||||
"serial_id": "1234",
|
||||
}
|
||||
|
||||
mock_entry = MockConfigEntry(
|
||||
domain="dsmr", unique_id="/dev/ttyUSB0", data=entry_data
|
||||
)
|
||||
|
||||
mock_entry.add_to_hass(hass)
|
||||
|
||||
await hass.config_entries.async_setup(mock_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
registry = await hass.helpers.entity_registry.async_get_registry()
|
||||
|
||||
entry = registry.async_get("sensor.power_consumption")
|
||||
assert entry
|
||||
assert entry.unique_id == "1234_Power_Consumption"
|
||||
|
||||
entry = registry.async_get("sensor.gas_consumption")
|
||||
assert not entry
|
||||
|
||||
|
||||
async def test_derivative():
|
||||
"""Test calculation of derivative value."""
|
||||
from dsmr_parser.objects import MBusObject
|
||||
|
||||
config = {"platform": "dsmr"}
|
||||
|
||||
entity = DerivativeDSMREntity("test", "1.0.0", config)
|
||||
entity = DerivativeDSMREntity("test", "test_device", "5678", "1.0.0", config)
|
||||
await entity.async_update()
|
||||
|
||||
assert entity.state is None, "initial state not unknown"
|
||||
|
@ -184,6 +225,8 @@ async def test_v4_meter(hass, dsmr_connection_fixture):
|
|||
"dsmr_version": "4",
|
||||
"precision": 4,
|
||||
"reconnect_interval": 30,
|
||||
"serial_id": "1234",
|
||||
"serial_id_gas": "5678",
|
||||
}
|
||||
|
||||
telegram = {
|
||||
|
@ -239,6 +282,8 @@ async def test_v5_meter(hass, dsmr_connection_fixture):
|
|||
"dsmr_version": "5",
|
||||
"precision": 4,
|
||||
"reconnect_interval": 30,
|
||||
"serial_id": "1234",
|
||||
"serial_id_gas": "5678",
|
||||
}
|
||||
|
||||
telegram = {
|
||||
|
@ -294,6 +339,8 @@ async def test_belgian_meter(hass, dsmr_connection_fixture):
|
|||
"dsmr_version": "5B",
|
||||
"precision": 4,
|
||||
"reconnect_interval": 30,
|
||||
"serial_id": "1234",
|
||||
"serial_id_gas": "5678",
|
||||
}
|
||||
|
||||
telegram = {
|
||||
|
@ -346,6 +393,8 @@ async def test_belgian_meter_low(hass, dsmr_connection_fixture):
|
|||
"dsmr_version": "5B",
|
||||
"precision": 4,
|
||||
"reconnect_interval": 30,
|
||||
"serial_id": "1234",
|
||||
"serial_id_gas": "5678",
|
||||
}
|
||||
|
||||
telegram = {ELECTRICITY_ACTIVE_TARIFF: CosemObject([{"value": "0002", "unit": ""}])}
|
||||
|
@ -383,6 +432,8 @@ async def test_tcp(hass, dsmr_connection_fixture):
|
|||
"dsmr_version": "2.2",
|
||||
"precision": 4,
|
||||
"reconnect_interval": 30,
|
||||
"serial_id": "1234",
|
||||
"serial_id_gas": "5678",
|
||||
}
|
||||
|
||||
mock_entry = MockConfigEntry(
|
||||
|
@ -407,6 +458,8 @@ async def test_connection_errors_retry(hass, dsmr_connection_fixture):
|
|||
"dsmr_version": "2.2",
|
||||
"precision": 4,
|
||||
"reconnect_interval": 0,
|
||||
"serial_id": "1234",
|
||||
"serial_id_gas": "5678",
|
||||
}
|
||||
|
||||
# override the mock to have it fail the first time and succeed after
|
||||
|
@ -442,6 +495,8 @@ async def test_reconnect(hass, dsmr_connection_fixture):
|
|||
"dsmr_version": "2.2",
|
||||
"precision": 4,
|
||||
"reconnect_interval": 0,
|
||||
"serial_id": "1234",
|
||||
"serial_id_gas": "5678",
|
||||
}
|
||||
|
||||
# mock waiting coroutine while connection lasts
|
||||
|
|
Loading…
Add table
Reference in a new issue