Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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
31 changes: 27 additions & 4 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,6 +26,7 @@

from .const import (
CONF_DSMR_VERSION,
CONF_IS_RFXTRX,
CONF_SERIAL_ID,
CONF_SERIAL_ID_GAS,
CONF_TIME_BETWEEN_UPDATE,
Expand All @@ -37,11 +42,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, is_rfxtrx: bool = False
) -> None:
"""Initialize."""
self._host = host
self._port = port
self._dsmr_version = dsmr_version
self._is_rfxtrx = is_rfxtrx
self._telegram: dict[str, DSMRObject] = {}
self._equipment_identifier = obis_ref.EQUIPMENT_IDENTIFIER
if dsmr_version == "5L":
Expand Down Expand Up @@ -77,15 +85,19 @@ def update_telegram(telegram: dict[str, DSMRObject]) -> None:

if self._host is None:
reader_factory = partial(
create_dsmr_reader,
create_dsmr_reader
if not self._is_rfxtrx
else create_rfxtrx_dsmr_reader,
self._port,
self._dsmr_version,
update_telegram,
loop=hass.loop,
)
else:
reader_factory = partial(
create_tcp_dsmr_reader,
create_tcp_dsmr_reader
if not self._is_rfxtrx
else create_rfxtrx_tcp_dsmr_reader,
self._host,
self._port,
self._dsmr_version,
Expand Down Expand Up @@ -114,7 +126,12 @@ async def _validate_dsmr_connection(
hass: core.HomeAssistant, data: dict[str, Any]
) -> 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],
data[CONF_IS_RFXTRX],
)

if not await conn.validate_connect(hass):
raise CannotConnect
Expand All @@ -138,6 +155,7 @@ class DSMRFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
VERSION = 1

_dsmr_version: str | None = None
_is_rfxtrx = False

@staticmethod
@callback
Expand Down Expand Up @@ -208,6 +226,7 @@ async def async_step_setup_network(
vol.Required(CONF_HOST): str,
vol.Required(CONF_PORT): int,
vol.Required(CONF_DSMR_VERSION): vol.In(DSMR_VERSIONS),
vol.Optional(CONF_IS_RFXTRX): bool,
Comment thread
rhpijnacker marked this conversation as resolved.
Outdated
}
)
return self.async_show_form(
Expand All @@ -225,6 +244,7 @@ async def async_step_setup_serial(
user_selection = user_input[CONF_PORT]
if user_selection == CONF_MANUAL_PATH:
self._dsmr_version = user_input[CONF_DSMR_VERSION]
self._is_rfxtrx = user_input[CONF_IS_RFXTRX]
return await self.async_step_setup_serial_manual_path()

dev_path = await self.hass.async_add_executor_job(
Expand All @@ -234,6 +254,7 @@ async def async_step_setup_serial(
validate_data = {
CONF_PORT: dev_path,
CONF_DSMR_VERSION: user_input[CONF_DSMR_VERSION],
CONF_IS_RFXTRX: user_input[CONF_IS_RFXTRX],
}

data = await self.async_validate_dsmr(validate_data, errors)
Expand All @@ -252,6 +273,7 @@ async def async_step_setup_serial(
{
vol.Required(CONF_PORT): vol.In(list_of_ports),
vol.Required(CONF_DSMR_VERSION): vol.In(DSMR_VERSIONS),
vol.Optional(CONF_IS_RFXTRX): bool,
}
)
return self.async_show_form(
Expand All @@ -268,6 +290,7 @@ async def async_step_setup_serial_manual_path(
validate_data = {
CONF_PORT: user_input[CONF_PORT],
CONF_DSMR_VERSION: self._dsmr_version,
CONF_IS_RFXTRX: self._is_rfxtrx,
}

errors: dict[str, str] = {}
Expand Down
1 change: 1 addition & 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_IS_RFXTRX = "is_rfxtrx"
CONF_RECONNECT_INTERVAL = "reconnect_interval"
CONF_PRECISION = "precision"
CONF_TIME_BETWEEN_UPDATE = "time_between_update"
Expand Down
12 changes: 10 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 @@ -28,6 +32,7 @@

from .const import (
CONF_DSMR_VERSION,
CONF_IS_RFXTRX,
CONF_PRECISION,
CONF_RECONNECT_INTERVAL,
CONF_SERIAL_ID,
Expand Down Expand Up @@ -77,9 +82,12 @@ 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
is_rfxtrx = False
if CONF_IS_RFXTRX in entry.data:
is_rfxtrx = entry.data[CONF_IS_RFXTRX]
Comment thread
rhpijnacker marked this conversation as resolved.
Outdated
if CONF_HOST in entry.data:
reader_factory = partial(
create_tcp_dsmr_reader,
create_tcp_dsmr_reader if not is_rfxtrx else create_rfxtrx_tcp_dsmr_reader,
entry.data[CONF_HOST],
entry.data[CONF_PORT],
dsmr_version,
Expand All @@ -89,7 +97,7 @@ def update_entities_telegram(telegram: dict[str, DSMRObject]) -> None:
)
else:
reader_factory = partial(
create_dsmr_reader,
create_dsmr_reader if not is_rfxtrx else create_rfxtrx_dsmr_reader,
entry.data[CONF_PORT],
dsmr_version,
update_entities_telegram,
Expand Down
6 changes: 4 additions & 2 deletions homeassistant/components/dsmr/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,16 @@
"data": {
"dsmr_version": "Select DSMR version",
"host": "Host",
"port": "Port"
"port": "Port",
"is_rfxtrx": "RFXtrx with integrated P1 reader"
Comment thread
rhpijnacker marked this conversation as resolved.
Outdated
},
"title": "Select connection address"
},
"setup_serial": {
"data": {
"dsmr_version": "Select DSMR version",
"port": "Select device"
"port": "Select device",
"is_rfxtrx": "RFXtrx with integrated P1 reader"
},
"title": "Device"
},
Expand Down
33 changes: 33 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 @@ -88,3 +89,35 @@ 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 RFXtrx 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 mocked out Asyncio classes."""
return (transport, protocol)

connection_factory = MagicMock(wraps=connection_factory)

async def wait_closed():
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,
):
yield (connection_factory, transport, protocol)
53 changes: 44 additions & 9 deletions tests/components/dsmr/test_config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,19 @@ async def test_setup_network(hass, dsmr_connection_send_validate_fixture):
with patch("homeassistant.components.dsmr.async_setup_entry", return_value=True):
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{"host": "10.10.0.1", "port": 1234, "dsmr_version": "2.2"},
{
"host": "10.10.0.1",
"port": 1234,
"dsmr_version": "2.2",
"is_rfxtrx": False,
},
)
Comment thread
rhpijnacker marked this conversation as resolved.

entry_data = {
"host": "10.10.0.1",
"port": 1234,
"dsmr_version": "2.2",
"is_rfxtrx": False,
}

assert result["type"] == "create_entry"
Expand Down Expand Up @@ -86,19 +92,44 @@ async def test_setup_serial(com_mock, hass, dsmr_connection_send_validate_fixtur

with patch("homeassistant.components.dsmr.async_setup_entry", return_value=True):
result = await hass.config_entries.flow.async_configure(
result["flow_id"], {"port": port.device, "dsmr_version": "2.2"}
result["flow_id"],
{"port": port.device, "dsmr_version": "2.2", "is_rfxtrx": False},
)

entry_data = {
"port": port.device,
"dsmr_version": "2.2",
}
entry_data = {"port": port.device, "dsmr_version": "2.2", "is_rfxtrx": False}

assert result["type"] == "create_entry"
assert result["title"] == port.device
assert result["data"] == {**entry_data, **SERIAL_DATA}


@patch("serial.tools.list_ports.comports", return_value=[com_port()])
async def test_setup_serial_rfxtrx(
com_mock, hass, rfxtrx_dsmr_connection_send_validate_fixture
):
"""Test we can setup serial."""
port = com_port()

result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)

result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{"type": "Serial"},
)

with patch("homeassistant.components.dsmr.async_setup_entry", return_value=True):
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{"port": port.device, "dsmr_version": "2.2", "is_rfxtrx": True},
)

entry_data = {"port": port.device, "dsmr_version": "2.2", "is_rfxtrx": True}

assert result["data"] == {**entry_data, **SERIAL_DATA}


@patch("serial.tools.list_ports.comports", return_value=[com_port()])
async def test_setup_serial_manual(
com_mock, hass, dsmr_connection_send_validate_fixture
Expand All @@ -122,7 +153,8 @@ async def test_setup_serial_manual(
assert result["errors"] == {}

result = await hass.config_entries.flow.async_configure(
result["flow_id"], {"port": "Enter Manually", "dsmr_version": "2.2"}
result["flow_id"],
{"port": "Enter Manually", "dsmr_version": "2.2", "is_rfxtrx": False},
)

assert result["type"] == "form"
Expand All @@ -137,6 +169,7 @@ async def test_setup_serial_manual(
entry_data = {
"port": "/dev/ttyUSB0",
"dsmr_version": "2.2",
"is_rfxtrx": False,
}

assert result["type"] == "create_entry"
Expand Down Expand Up @@ -179,7 +212,8 @@ async def test_setup_serial_fail(com_mock, hass, dsmr_connection_send_validate_f
first_fail_connection_factory,
):
result = await hass.config_entries.flow.async_configure(
result["flow_id"], {"port": port.device, "dsmr_version": "2.2"}
result["flow_id"],
{"port": port.device, "dsmr_version": "2.2", "is_rfxtrx": False},
)

assert result["type"] == "form"
Expand Down Expand Up @@ -216,7 +250,8 @@ async def test_setup_serial_wrong_telegram(
assert result["errors"] == {}

result = await hass.config_entries.flow.async_configure(
result["flow_id"], {"port": port.device, "dsmr_version": "2.2"}
result["flow_id"],
{"port": port.device, "dsmr_version": "2.2", "is_rfxtrx": False},
)

assert result["type"] == "form"
Expand Down