Skip to content
Merged
Show file tree
Hide file tree
Changes from 35 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
25b3ed3
Read DSMR telegrams over RFXtrx connection
rhpijnacker Dec 31, 2021
b5765d1
Fix tests
rhpijnacker Jan 1, 2022
1beade9
Add test case for RFXtrx variation
rhpijnacker Jan 1, 2022
496ccbc
Read DSMR telegrams over RFXtrx connection
rhpijnacker Dec 31, 2021
ebad8bc
Fix tests
rhpijnacker Jan 1, 2022
5544590
Add test case for RFXtrx variation
rhpijnacker Jan 1, 2022
1810264
Merge branch 'feature/dsmr-rfxtrx' of https://github.com/rhpijnacker/…
rhpijnacker Jan 6, 2022
923a4ed
Revert translation in en.json
rhpijnacker Jan 6, 2022
bdae65e
Add UI strings
rhpijnacker Jan 6, 2022
60785e2
Make CONF_IS_RFXTRX required
rhpijnacker Jan 6, 2022
6688ed2
Add sensor test case for RFXtrx
rhpijnacker Jan 6, 2022
2f4d380
Review improvement
rhpijnacker Jan 6, 2022
c242bf0
Merge remote-tracking branch 'upstream/dev' into feature/dsmr-rfxtrx
rhpijnacker Jan 12, 2022
f19ac66
Fix merged-in test cases
rhpijnacker Jan 12, 2022
5306c0d
Merge branch 'dev' of https://github.com/home-assistant/core into fea…
rhpijnacker Jan 21, 2022
3aa5e7f
Auto-detect RFXtrx by retrying
rhpijnacker Jan 22, 2022
225de65
Remove unused string resource
rhpijnacker Jan 22, 2022
2a89bed
As requested in a review comment
rhpijnacker Jan 22, 2022
2e82c04
Change `is_rfxtrx` with `protocol` parameter
rhpijnacker Jan 23, 2022
fa76a68
More review comments
rhpijnacker Jan 25, 2022
403aaf2
More review comments
rhpijnacker Jan 25, 2022
bc56839
Add unit test for RFXtrx
rhpijnacker Jan 25, 2022
713c03a
Clean-up unused functio
rhpijnacker Jan 30, 2022
c8250ff
Merge branch 'home-assistant:dev' into feature/dsmr-rfxtrx
rhpijnacker Feb 9, 2022
661f99a
Merge branch 'home-assistant:dev' into feature/dsmr-rfxtrx
rhpijnacker Feb 10, 2022
a757100
Merge remote-tracking branch 'upstream/dev' into feature/dsmr-rfxtrx
rhpijnacker Mar 20, 2022
f136158
Improve code cohesion
rhpijnacker Mar 20, 2022
df56a4a
Fix failing unit test
rhpijnacker Mar 20, 2022
234bdf6
Update tests/components/dsmr/test_config_flow.py
rhpijnacker Mar 21, 2022
452c3bc
Merge branch 'home-assistant:dev' into feature/dsmr-rfxtrx
rhpijnacker Mar 23, 2022
25bcb80
Merge branch 'dev' into feature/dsmr-rfxtrx
rhpijnacker Mar 24, 2022
d90df55
Merge branch 'home-assistant:dev' into feature/dsmr-rfxtrx
rhpijnacker Mar 25, 2022
f2e7063
Merge branch 'home-assistant:dev' into feature/dsmr-rfxtrx
rhpijnacker Mar 25, 2022
a160519
Block until all tasks are finished
rhpijnacker Mar 26, 2022
b332b45
Merge branch 'home-assistant:dev' into feature/dsmr-rfxtrx
rhpijnacker Mar 26, 2022
ddfe2bd
Clean up
MartinHjelmare Mar 26, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 35 additions & 7 deletions homeassistant/components/dsmr/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
from async_timeout import timeout
from dsmr_parser import obis_references as obis_ref
from dsmr_parser.clients.protocol import create_dsmr_reader, create_tcp_dsmr_reader
from dsmr_parser.clients.rfxtrx_protocol import (
create_rfxtrx_dsmr_reader,
create_rfxtrx_tcp_dsmr_reader,
)
from dsmr_parser.objects import DSMRObject
import serial
import serial.tools.list_ports
Expand All @@ -22,13 +26,16 @@

from .const import (
CONF_DSMR_VERSION,
CONF_PROTOCOL,
CONF_SERIAL_ID,
CONF_SERIAL_ID_GAS,
CONF_TIME_BETWEEN_UPDATE,
DEFAULT_TIME_BETWEEN_UPDATE,
DOMAIN,
DSMR_PROTOCOL,
DSMR_VERSIONS,
LOGGER,
RFXTRX_DSMR_PROTOCOL,
)

CONF_MANUAL_PATH = "Enter Manually"
Expand All @@ -37,11 +44,14 @@
class DSMRConnection:
"""Test the connection to DSMR and receive telegram to read serial ids."""

def __init__(self, host: str | None, port: int, dsmr_version: str) -> None:
def __init__(
self, host: str | None, port: int, dsmr_version: str, protocol: str
) -> None:
"""Initialize."""
self._host = host
self._port = port
self._dsmr_version = dsmr_version
self._protocol = protocol
self._telegram: dict[str, DSMRObject] = {}
self._equipment_identifier = obis_ref.EQUIPMENT_IDENTIFIER
if dsmr_version == "5L":
Expand Down Expand Up @@ -78,16 +88,24 @@ def update_telegram(telegram: dict[str, DSMRObject]) -> None:
transport.close()

if self._host is None:
if self._protocol == DSMR_PROTOCOL:
create_reader = create_dsmr_reader
else:
create_reader = create_rfxtrx_dsmr_reader
reader_factory = partial(
create_dsmr_reader,
create_reader,
self._port,
self._dsmr_version,
update_telegram,
loop=hass.loop,
)
else:
if self._protocol == DSMR_PROTOCOL:
create_reader = create_tcp_dsmr_reader
else:
create_reader = create_rfxtrx_tcp_dsmr_reader
reader_factory = partial(
create_tcp_dsmr_reader,
create_reader,
self._host,
self._port,
self._dsmr_version,
Expand All @@ -113,10 +131,15 @@ def update_telegram(telegram: dict[str, DSMRObject]) -> None:


async def _validate_dsmr_connection(
hass: core.HomeAssistant, data: dict[str, Any]
hass: core.HomeAssistant, data: dict[str, Any], protocol: str
) -> dict[str, str | None]:
"""Validate the user input allows us to connect."""
conn = DSMRConnection(data.get(CONF_HOST), data[CONF_PORT], data[CONF_DSMR_VERSION])
conn = DSMRConnection(
data.get(CONF_HOST),
data[CONF_PORT],
data[CONF_DSMR_VERSION],
protocol,
)

if not await conn.validate_connect(hass):
raise CannotConnect
Expand Down Expand Up @@ -260,9 +283,14 @@ async def async_validate_dsmr(
data = input_data

try:
info = await _validate_dsmr_connection(self.hass, data)
try:
protocol = DSMR_PROTOCOL
info = await _validate_dsmr_connection(self.hass, data, protocol)
except CannotCommunicate:
protocol = RFXTRX_DSMR_PROTOCOL
info = await _validate_dsmr_connection(self.hass, data, protocol)

data = {**data, **info}
data = {**data, **info, CONF_PROTOCOL: protocol}

if info[CONF_SERIAL_ID]:
await self.async_set_unique_id(info[CONF_SERIAL_ID])
Expand Down
4 changes: 4 additions & 0 deletions homeassistant/components/dsmr/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

PLATFORMS = [Platform.SENSOR]
CONF_DSMR_VERSION = "dsmr_version"
CONF_PROTOCOL = "protocol"
CONF_RECONNECT_INTERVAL = "reconnect_interval"
CONF_PRECISION = "precision"
CONF_TIME_BETWEEN_UPDATE = "time_between_update"
Expand All @@ -37,6 +38,9 @@

DSMR_VERSIONS = {"2.2", "4", "5", "5B", "5L", "5S", "Q3D"}

DSMR_PROTOCOL = "dsmr_protocol"
RFXTRX_DSMR_PROTOCOL = "rfxtrx_dsmr_protocol"

SENSORS: tuple[DSMRSensorEntityDescription, ...] = (
DSMRSensorEntityDescription(
key=obis_references.CURRENT_ELECTRICITY_USAGE,
Expand Down
19 changes: 17 additions & 2 deletions homeassistant/components/dsmr/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@

from dsmr_parser import obis_references as obis_ref
from dsmr_parser.clients.protocol import create_dsmr_reader, create_tcp_dsmr_reader
from dsmr_parser.clients.rfxtrx_protocol import (
create_rfxtrx_dsmr_reader,
create_rfxtrx_tcp_dsmr_reader,
)
from dsmr_parser.objects import DSMRObject
import serial

Expand All @@ -29,6 +33,7 @@
from .const import (
CONF_DSMR_VERSION,
CONF_PRECISION,
CONF_PROTOCOL,
CONF_RECONNECT_INTERVAL,
CONF_SERIAL_ID,
CONF_SERIAL_ID_GAS,
Expand All @@ -40,6 +45,7 @@
DEVICE_NAME_ELECTRICITY,
DEVICE_NAME_GAS,
DOMAIN,
DSMR_PROTOCOL,
LOGGER,
SENSORS,
)
Expand Down Expand Up @@ -77,9 +83,14 @@ def update_entities_telegram(telegram: dict[str, DSMRObject]) -> None:

# Creates an asyncio.Protocol factory for reading DSMR telegrams from
# serial and calls update_entities_telegram to update entities on arrival
protocol = entry.data.get(CONF_PROTOCOL, DSMR_PROTOCOL)
if CONF_HOST in entry.data:
if protocol == DSMR_PROTOCOL:
create_reader = create_tcp_dsmr_reader
else:
create_reader = create_rfxtrx_tcp_dsmr_reader
reader_factory = partial(
create_tcp_dsmr_reader,
create_reader,
entry.data[CONF_HOST],
entry.data[CONF_PORT],
dsmr_version,
Expand All @@ -88,8 +99,12 @@ def update_entities_telegram(telegram: dict[str, DSMRObject]) -> None:
keep_alive_interval=60,
)
else:
if protocol == DSMR_PROTOCOL:
create_reader = create_dsmr_reader
else:
create_reader = create_rfxtrx_dsmr_reader
reader_factory = partial(
create_dsmr_reader,
create_reader,
entry.data[CONF_PORT],
dsmr_version,
update_entities_telegram,
Expand Down
64 changes: 64 additions & 0 deletions tests/components/dsmr/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from unittest.mock import MagicMock, patch

from dsmr_parser.clients.protocol import DSMRProtocol
from dsmr_parser.clients.rfxtrx_protocol import RFXtrxDSMRProtocol
from dsmr_parser.obis_references import (
EQUIPMENT_IDENTIFIER,
EQUIPMENT_IDENTIFIER_GAS,
Expand Down Expand Up @@ -36,6 +37,29 @@ async def connection_factory(*args, **kwargs):
yield (connection_factory, transport, protocol)


@pytest.fixture
async def rfxtrx_dsmr_connection_fixture(hass):
"""Fixture that mocks RFXtrx connection."""

transport = MagicMock(spec=asyncio.Transport)
protocol = MagicMock(spec=RFXtrxDSMRProtocol)

async def connection_factory(*args, **kwargs):
"""Return mocked out Asyncio classes."""
return (transport, protocol)

connection_factory = MagicMock(wraps=connection_factory)

with patch(
"homeassistant.components.dsmr.sensor.create_rfxtrx_dsmr_reader",
connection_factory,
), patch(
"homeassistant.components.dsmr.sensor.create_rfxtrx_tcp_dsmr_reader",
connection_factory,
):
yield (connection_factory, transport, protocol)


@pytest.fixture
async def dsmr_connection_send_validate_fixture(hass):
"""Fixture that mocks serial connection."""
Expand Down Expand Up @@ -95,3 +119,43 @@ async def wait_closed():
connection_factory,
):
yield (connection_factory, transport, protocol)


@pytest.fixture
async def rfxtrx_dsmr_connection_send_validate_fixture(hass):
"""Fixture that mocks serial connection."""

transport = MagicMock(spec=asyncio.Transport)
protocol = MagicMock(spec=RFXtrxDSMRProtocol)

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

async def connection_factory(*args, **kwargs):
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
telegram_callback = connection_factory.call_args_list[0][0][3]
else:
# Serial
telegram_callback = connection_factory.call_args_list[0][0][2]

telegram_callback(protocol.telegram)

protocol.wait_closed = wait_closed

with patch(
"homeassistant.components.dsmr.config_flow.create_rfxtrx_dsmr_reader",
connection_factory,
), patch(
"homeassistant.components.dsmr.config_flow.create_rfxtrx_tcp_dsmr_reader",
connection_factory,
):
yield (connection_factory, transport, protocol)
Loading