mirror of
https://github.com/home-assistant/core.git
synced 2025-07-17 10:17:09 +00:00
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
@ -35,11 +35,15 @@ class DSMRConnection:
|
|||||||
self._port = port
|
self._port = port
|
||||||
self._dsmr_version = dsmr_version
|
self._dsmr_version = dsmr_version
|
||||||
self._telegram = {}
|
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):
|
def equipment_identifier(self):
|
||||||
"""Equipment identifier."""
|
"""Equipment identifier."""
|
||||||
if obis_ref.EQUIPMENT_IDENTIFIER in self._telegram:
|
if self._equipment_identifier in self._telegram:
|
||||||
dsmr_object = self._telegram[obis_ref.EQUIPMENT_IDENTIFIER]
|
dsmr_object = self._telegram[self._equipment_identifier]
|
||||||
return getattr(dsmr_object, "value", None)
|
return getattr(dsmr_object, "value", None)
|
||||||
|
|
||||||
def equipment_identifier_gas(self):
|
def equipment_identifier_gas(self):
|
||||||
@ -52,7 +56,7 @@ class DSMRConnection:
|
|||||||
"""Test if we can validate connection with the device."""
|
"""Test if we can validate connection with the device."""
|
||||||
|
|
||||||
def update_telegram(telegram):
|
def update_telegram(telegram):
|
||||||
if obis_ref.EQUIPMENT_IDENTIFIER in telegram:
|
if self._equipment_identifier in telegram:
|
||||||
self._telegram = telegram
|
self._telegram = telegram
|
||||||
transport.close()
|
transport.close()
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
|||||||
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.string,
|
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.string,
|
||||||
vol.Optional(CONF_HOST): cv.string,
|
vol.Optional(CONF_HOST): cv.string,
|
||||||
vol.Optional(CONF_DSMR_VERSION, default=DEFAULT_DSMR_VERSION): vol.All(
|
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_RECONNECT_INTERVAL, default=DEFAULT_RECONNECT_INTERVAL): int,
|
||||||
vol.Optional(CONF_PRECISION, default=DEFAULT_PRECISION): vol.Coerce(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 Consumption", obis_ref.CURRENT_ELECTRICITY_USAGE],
|
||||||
["Power Production", obis_ref.CURRENT_ELECTRICITY_DELIVERY],
|
["Power Production", obis_ref.CURRENT_ELECTRICITY_DELIVERY],
|
||||||
["Power Tariff", obis_ref.ELECTRICITY_ACTIVE_TARIFF],
|
["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 1)", obis_ref.ELECTRICITY_USED_TARIFF_1],
|
||||||
["Energy Consumption (tarif 2)", obis_ref.ELECTRICITY_USED_TARIFF_2],
|
["Energy Consumption (tarif 2)", obis_ref.ELECTRICITY_USED_TARIFF_2],
|
||||||
["Energy Production (tarif 1)", obis_ref.ELECTRICITY_DELIVERED_TARIFF_1],
|
["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],
|
["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
|
# Generate device entities
|
||||||
devices = [
|
devices = [
|
||||||
DSMREntity(name, DEVICE_NAME_ENERGY, config[CONF_SERIAL_ID], obis, config)
|
DSMREntity(name, DEVICE_NAME_ENERGY, config[CONF_SERIAL_ID], obis, config)
|
||||||
@ -120,7 +137,7 @@ async def async_setup_entry(
|
|||||||
|
|
||||||
# Protocol version specific obis
|
# Protocol version specific obis
|
||||||
if CONF_SERIAL_ID_GAS in config:
|
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
|
gas_obis = obis_ref.HOURLY_GAS_METER_READING
|
||||||
elif dsmr_version in ("5B",):
|
elif dsmr_version in ("5B",):
|
||||||
gas_obis = obis_ref.BELGIUM_HOURLY_GAS_METER_READING
|
gas_obis = obis_ref.BELGIUM_HOURLY_GAS_METER_READING
|
||||||
|
@ -2,7 +2,11 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
from dsmr_parser.clients.protocol import DSMRProtocol
|
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
|
from dsmr_parser.objects import CosemObject
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
@ -38,17 +42,27 @@ async def dsmr_connection_send_validate_fixture(hass):
|
|||||||
transport = MagicMock(spec=asyncio.Transport)
|
transport = MagicMock(spec=asyncio.Transport)
|
||||||
protocol = MagicMock(spec=DSMRProtocol)
|
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 = {
|
protocol.telegram = {
|
||||||
EQUIPMENT_IDENTIFIER: CosemObject([{"value": "12345678", "unit": ""}]),
|
EQUIPMENT_IDENTIFIER: CosemObject([{"value": "12345678", "unit": ""}]),
|
||||||
EQUIPMENT_IDENTIFIER_GAS: CosemObject([{"value": "123456789", "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():
|
async def wait_closed():
|
||||||
if isinstance(connection_factory.call_args_list[0][0][2], str):
|
if isinstance(connection_factory.call_args_list[0][0][2], str):
|
||||||
# TCP
|
# TCP
|
||||||
|
@ -242,3 +242,26 @@ async def test_options_flow(hass):
|
|||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert entry.options == {"time_between_update": 15}
|
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
|
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):
|
async def test_belgian_meter(hass, dsmr_connection_fixture):
|
||||||
"""Test if Belgian meter is correctly parsed."""
|
"""Test if Belgian meter is correctly parsed."""
|
||||||
(connection_factory, transport, protocol) = dsmr_connection_fixture
|
(connection_factory, transport, protocol) = dsmr_connection_fixture
|
||||||
|
Loading…
x
Reference in New Issue
Block a user