Skip to content

Commit

Permalink
Add explicit support for Luxembourg Smarty meter in dsmr integration (h…
Browse files Browse the repository at this point in the history
…ome-assistant#43975)

* Add support for Luxembourg Smarty meter

* Add config flow test

* Add sensor tests
  • Loading branch information
RobBie1221 committed Dec 27, 2020
1 parent 1f27fb4 commit 9531b08
Show file tree
Hide file tree
Showing 5 changed files with 139 additions and 12 deletions.
10 changes: 7 additions & 3 deletions homeassistant/components/dsmr/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,15 @@ def __init__(self, host, port, dsmr_version):
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):
Expand All @@ -52,7 +56,7 @@ async def validate_connect(self, hass: core.HomeAssistant) -> bool:
"""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()

Expand Down
23 changes: 20 additions & 3 deletions homeassistant/components/dsmr/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
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),
Expand Down Expand Up @@ -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],
Expand All @@ -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)
Expand All @@ -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
Expand Down
26 changes: 20 additions & 6 deletions tests/components/dsmr/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -38,17 +42,27 @@ async def dsmr_connection_send_validate_fixture(hass):
transport = MagicMock(spec=asyncio.Transport)
protocol = MagicMock(spec=DSMRProtocol)

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)

protocol.telegram = {
EQUIPMENT_IDENTIFIER: CosemObject([{"value": "12345678", "unit": ""}]),
EQUIPMENT_IDENTIFIER_GAS: CosemObject([{"value": "123456789", "unit": ""}]),
}

async def wait_closed():
if isinstance(connection_factory.call_args_list[0][0][2], str):
# TCP
Expand Down
23 changes: 23 additions & 0 deletions tests/components/dsmr/test_config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}
69 changes: 69 additions & 0 deletions tests/components/dsmr/test_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 9531b08

Please sign in to comment.