Add explicit support for Luxembourg Smarty meter in dsmr integration (#43975)
* Add support for Luxembourg Smarty meter * Add config flow test * Add sensor tests
This commit is contained in:
parent
1f27fb4644
commit
9531b08f2a
5 changed files with 140 additions and 13 deletions
|
@ -35,11 +35,15 @@ class DSMRConnection:
|
|||
self._port = port
|
||||
self._dsmr_version = dsmr_version
|
||||
self._telegram = {}
|
||||
if dsmr_version == "5L":
|
||||
self._equipment_identifier = obis_ref.LUXEMBOURG_EQUIPMENT_IDENTIFIER
|
||||
else:
|
||||
self._equipment_identifier = obis_ref.EQUIPMENT_IDENTIFIER
|
||||
|
||||
def equipment_identifier(self):
|
||||
"""Equipment identifier."""
|
||||
if obis_ref.EQUIPMENT_IDENTIFIER in self._telegram:
|
||||
dsmr_object = self._telegram[obis_ref.EQUIPMENT_IDENTIFIER]
|
||||
if self._equipment_identifier in self._telegram:
|
||||
dsmr_object = self._telegram[self._equipment_identifier]
|
||||
return getattr(dsmr_object, "value", None)
|
||||
|
||||
def equipment_identifier_gas(self):
|
||||
|
@ -52,7 +56,7 @@ class DSMRConnection:
|
|||
"""Test if we can validate connection with the device."""
|
||||
|
||||
def update_telegram(telegram):
|
||||
if obis_ref.EQUIPMENT_IDENTIFIER in telegram:
|
||||
if self._equipment_identifier in telegram:
|
||||
self._telegram = telegram
|
||||
transport.close()
|
||||
|
||||
|
|
|
@ -54,7 +54,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
|||
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.string,
|
||||
vol.Optional(CONF_HOST): cv.string,
|
||||
vol.Optional(CONF_DSMR_VERSION, default=DEFAULT_DSMR_VERSION): vol.All(
|
||||
cv.string, vol.In(["5B", "5", "4", "2.2"])
|
||||
cv.string, vol.In(["5L", "5B", "5", "4", "2.2"])
|
||||
),
|
||||
vol.Optional(CONF_RECONNECT_INTERVAL, default=DEFAULT_RECONNECT_INTERVAL): int,
|
||||
vol.Optional(CONF_PRECISION, default=DEFAULT_PRECISION): vol.Coerce(int),
|
||||
|
@ -85,7 +85,6 @@ async def async_setup_entry(
|
|||
["Power Consumption", obis_ref.CURRENT_ELECTRICITY_USAGE],
|
||||
["Power Production", obis_ref.CURRENT_ELECTRICITY_DELIVERY],
|
||||
["Power Tariff", obis_ref.ELECTRICITY_ACTIVE_TARIFF],
|
||||
["Energy Consumption (total)", obis_ref.ELECTRICITY_IMPORTED_TOTAL],
|
||||
["Energy Consumption (tarif 1)", obis_ref.ELECTRICITY_USED_TARIFF_1],
|
||||
["Energy Consumption (tarif 2)", obis_ref.ELECTRICITY_USED_TARIFF_2],
|
||||
["Energy Production (tarif 1)", obis_ref.ELECTRICITY_DELIVERED_TARIFF_1],
|
||||
|
@ -112,6 +111,24 @@ async def async_setup_entry(
|
|||
["Current Phase L3", obis_ref.INSTANTANEOUS_CURRENT_L3],
|
||||
]
|
||||
|
||||
if dsmr_version == "5L":
|
||||
obis_mapping.extend(
|
||||
[
|
||||
[
|
||||
"Energy Consumption (total)",
|
||||
obis_ref.LUXEMBOURG_ELECTRICITY_USED_TARIFF_GLOBAL,
|
||||
],
|
||||
[
|
||||
"Energy Production (total)",
|
||||
obis_ref.LUXEMBOURG_ELECTRICITY_DELIVERED_TARIFF_GLOBAL,
|
||||
],
|
||||
]
|
||||
)
|
||||
else:
|
||||
obis_mapping.extend(
|
||||
[["Energy Consumption (total)", obis_ref.ELECTRICITY_IMPORTED_TOTAL]]
|
||||
)
|
||||
|
||||
# Generate device entities
|
||||
devices = [
|
||||
DSMREntity(name, DEVICE_NAME_ENERGY, config[CONF_SERIAL_ID], obis, config)
|
||||
|
@ -120,7 +137,7 @@ async def async_setup_entry(
|
|||
|
||||
# Protocol version specific obis
|
||||
if CONF_SERIAL_ID_GAS in config:
|
||||
if dsmr_version in ("4", "5"):
|
||||
if dsmr_version in ("4", "5", "5L"):
|
||||
gas_obis = obis_ref.HOURLY_GAS_METER_READING
|
||||
elif dsmr_version in ("5B",):
|
||||
gas_obis = obis_ref.BELGIUM_HOURLY_GAS_METER_READING
|
||||
|
|
|
@ -2,7 +2,11 @@
|
|||
import asyncio
|
||||
|
||||
from dsmr_parser.clients.protocol import DSMRProtocol
|
||||
from dsmr_parser.obis_references import EQUIPMENT_IDENTIFIER, EQUIPMENT_IDENTIFIER_GAS
|
||||
from dsmr_parser.obis_references import (
|
||||
EQUIPMENT_IDENTIFIER,
|
||||
EQUIPMENT_IDENTIFIER_GAS,
|
||||
LUXEMBOURG_EQUIPMENT_IDENTIFIER,
|
||||
)
|
||||
from dsmr_parser.objects import CosemObject
|
||||
import pytest
|
||||
|
||||
|
@ -38,17 +42,27 @@ async def dsmr_connection_send_validate_fixture(hass):
|
|||
transport = MagicMock(spec=asyncio.Transport)
|
||||
protocol = MagicMock(spec=DSMRProtocol)
|
||||
|
||||
async def connection_factory(*args, **kwargs):
|
||||
"""Return mocked out Asyncio classes."""
|
||||
return (transport, protocol)
|
||||
|
||||
connection_factory = MagicMock(wraps=connection_factory)
|
||||
|
||||
protocol.telegram = {
|
||||
EQUIPMENT_IDENTIFIER: CosemObject([{"value": "12345678", "unit": ""}]),
|
||||
EQUIPMENT_IDENTIFIER_GAS: CosemObject([{"value": "123456789", "unit": ""}]),
|
||||
}
|
||||
|
||||
async def connection_factory(*args, **kwargs):
|
||||
"""Return mocked out Asyncio classes."""
|
||||
if args[1] == "5L":
|
||||
protocol.telegram = {
|
||||
LUXEMBOURG_EQUIPMENT_IDENTIFIER: CosemObject(
|
||||
[{"value": "12345678", "unit": ""}]
|
||||
),
|
||||
EQUIPMENT_IDENTIFIER_GAS: CosemObject(
|
||||
[{"value": "123456789", "unit": ""}]
|
||||
),
|
||||
}
|
||||
|
||||
return (transport, protocol)
|
||||
|
||||
connection_factory = MagicMock(wraps=connection_factory)
|
||||
|
||||
async def wait_closed():
|
||||
if isinstance(connection_factory.call_args_list[0][0][2], str):
|
||||
# TCP
|
||||
|
|
|
@ -242,3 +242,26 @@ async def test_options_flow(hass):
|
|||
await hass.async_block_till_done()
|
||||
|
||||
assert entry.options == {"time_between_update": 15}
|
||||
|
||||
|
||||
async def test_import_luxembourg(hass, dsmr_connection_send_validate_fixture):
|
||||
"""Test we can import."""
|
||||
await setup.async_setup_component(hass, "persistent_notification", {})
|
||||
|
||||
entry_data = {
|
||||
"port": "/dev/ttyUSB0",
|
||||
"dsmr_version": "5L",
|
||||
"precision": 4,
|
||||
"reconnect_interval": 30,
|
||||
}
|
||||
|
||||
with patch("homeassistant.components.dsmr.async_setup_entry", return_value=True):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_IMPORT},
|
||||
data=entry_data,
|
||||
)
|
||||
|
||||
assert result["type"] == "create_entry"
|
||||
assert result["title"] == "/dev/ttyUSB0"
|
||||
assert result["data"] == {**entry_data, **SERIAL_DATA}
|
||||
|
|
|
@ -337,6 +337,75 @@ async def test_v5_meter(hass, dsmr_connection_fixture):
|
|||
assert gas_consumption.attributes.get("unit_of_measurement") == VOLUME_CUBIC_METERS
|
||||
|
||||
|
||||
async def test_luxembourg_meter(hass, dsmr_connection_fixture):
|
||||
"""Test if v5 meter is correctly parsed."""
|
||||
(connection_factory, transport, protocol) = dsmr_connection_fixture
|
||||
|
||||
from dsmr_parser.obis_references import (
|
||||
HOURLY_GAS_METER_READING,
|
||||
LUXEMBOURG_ELECTRICITY_DELIVERED_TARIFF_GLOBAL,
|
||||
LUXEMBOURG_ELECTRICITY_USED_TARIFF_GLOBAL,
|
||||
)
|
||||
from dsmr_parser.objects import CosemObject, MBusObject
|
||||
|
||||
entry_data = {
|
||||
"port": "/dev/ttyUSB0",
|
||||
"dsmr_version": "5L",
|
||||
"precision": 4,
|
||||
"reconnect_interval": 30,
|
||||
"serial_id": "1234",
|
||||
"serial_id_gas": "5678",
|
||||
}
|
||||
entry_options = {
|
||||
"time_between_update": 0,
|
||||
}
|
||||
|
||||
telegram = {
|
||||
HOURLY_GAS_METER_READING: MBusObject(
|
||||
[
|
||||
{"value": datetime.datetime.fromtimestamp(1551642213)},
|
||||
{"value": Decimal(745.695), "unit": VOLUME_CUBIC_METERS},
|
||||
]
|
||||
),
|
||||
LUXEMBOURG_ELECTRICITY_USED_TARIFF_GLOBAL: CosemObject(
|
||||
[{"value": Decimal(123.456), "unit": ENERGY_KILO_WATT_HOUR}]
|
||||
),
|
||||
LUXEMBOURG_ELECTRICITY_DELIVERED_TARIFF_GLOBAL: CosemObject(
|
||||
[{"value": Decimal(654.321), "unit": ENERGY_KILO_WATT_HOUR}]
|
||||
),
|
||||
}
|
||||
|
||||
mock_entry = MockConfigEntry(
|
||||
domain="dsmr", unique_id="/dev/ttyUSB0", data=entry_data, options=entry_options
|
||||
)
|
||||
|
||||
mock_entry.add_to_hass(hass)
|
||||
|
||||
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 update
|
||||
await asyncio.sleep(0)
|
||||
|
||||
power_tariff = hass.states.get("sensor.energy_consumption_total")
|
||||
assert power_tariff.state == "123.456"
|
||||
assert power_tariff.attributes.get("unit_of_measurement") == ENERGY_KILO_WATT_HOUR
|
||||
|
||||
power_tariff = hass.states.get("sensor.energy_production_total")
|
||||
assert power_tariff.state == "654.321"
|
||||
assert power_tariff.attributes.get("unit_of_measurement") == ENERGY_KILO_WATT_HOUR
|
||||
|
||||
# check if gas consumption is parsed correctly
|
||||
gas_consumption = hass.states.get("sensor.gas_consumption")
|
||||
assert gas_consumption.state == "745.695"
|
||||
assert gas_consumption.attributes.get("unit_of_measurement") == VOLUME_CUBIC_METERS
|
||||
|
||||
|
||||
async def test_belgian_meter(hass, dsmr_connection_fixture):
|
||||
"""Test if Belgian meter is correctly parsed."""
|
||||
(connection_factory, transport, protocol) = dsmr_connection_fixture
|
||||
|
|
Loading…
Add table
Reference in a new issue