From 25b3ed35bd56be702a07e3ac820f566ee7dbeb49 Mon Sep 17 00:00:00 2001 From: Ronald Pijnacker Date: Fri, 31 Dec 2021 07:24:12 +0000 Subject: [PATCH 01/25] Read DSMR telegrams over RFXtrx connection --- homeassistant/components/dsmr/config_flow.py | 31 ++++++++++++++++--- homeassistant/components/dsmr/const.py | 1 + homeassistant/components/dsmr/sensor.py | 12 +++++-- .../components/dsmr/translations/en.json | 6 ++-- 4 files changed, 42 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/dsmr/config_flow.py b/homeassistant/components/dsmr/config_flow.py index 587d51d13c7d56..27a63bce95c8a2 100644 --- a/homeassistant/components/dsmr/config_flow.py +++ b/homeassistant/components/dsmr/config_flow.py @@ -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 @@ -22,6 +26,7 @@ from .const import ( CONF_DSMR_VERSION, + CONF_IS_RFXTRX, CONF_SERIAL_ID, CONF_SERIAL_ID_GAS, CONF_TIME_BETWEEN_UPDATE, @@ -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": @@ -77,7 +85,9 @@ 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, @@ -85,7 +95,9 @@ def update_telegram(telegram: dict[str, DSMRObject]) -> None: ) 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, @@ -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 @@ -138,6 +155,7 @@ class DSMRFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): VERSION = 1 _dsmr_version: str | None = None + _is_rfxtrx = False @staticmethod @callback @@ -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, } ) return self.async_show_form( @@ -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( @@ -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) @@ -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( @@ -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] = {} diff --git a/homeassistant/components/dsmr/const.py b/homeassistant/components/dsmr/const.py index a8c4de930dfa9f..9561c1e98faf37 100644 --- a/homeassistant/components/dsmr/const.py +++ b/homeassistant/components/dsmr/const.py @@ -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" diff --git a/homeassistant/components/dsmr/sensor.py b/homeassistant/components/dsmr/sensor.py index 94ae2864905bd1..3da7679ef6766e 100644 --- a/homeassistant/components/dsmr/sensor.py +++ b/homeassistant/components/dsmr/sensor.py @@ -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 @@ -28,6 +32,7 @@ from .const import ( CONF_DSMR_VERSION, + CONF_IS_RFXTRX, CONF_PRECISION, CONF_RECONNECT_INTERVAL, CONF_SERIAL_ID, @@ -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] 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, @@ -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, diff --git a/homeassistant/components/dsmr/translations/en.json b/homeassistant/components/dsmr/translations/en.json index 6f873729bc84bd..9f095166d4d346 100644 --- a/homeassistant/components/dsmr/translations/en.json +++ b/homeassistant/components/dsmr/translations/en.json @@ -15,14 +15,16 @@ "data": { "dsmr_version": "Select DSMR version", "host": "Host", - "port": "Port" + "port": "Port", + "is_rfxtrx": "RFXtrx with integrated P1 reader" }, "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" }, From b5765d1e13d01e7fe47ca4e47b2f23d997e6ff56 Mon Sep 17 00:00:00 2001 From: Ronald Pijnacker Date: Sat, 1 Jan 2022 08:33:57 +0000 Subject: [PATCH 02/25] Fix tests --- tests/components/dsmr/test_config_flow.py | 26 +++++++++++++++-------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/tests/components/dsmr/test_config_flow.py b/tests/components/dsmr/test_config_flow.py index 02c27369f09e87..5d1ecf390a93ea 100644 --- a/tests/components/dsmr/test_config_flow.py +++ b/tests/components/dsmr/test_config_flow.py @@ -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, + }, ) entry_data = { "host": "10.10.0.1", "port": 1234, "dsmr_version": "2.2", + "is_rfxtrx": False, } assert result["type"] == "create_entry" @@ -86,13 +92,11 @@ 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 @@ -122,7 +126,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" @@ -137,6 +142,7 @@ async def test_setup_serial_manual( entry_data = { "port": "/dev/ttyUSB0", "dsmr_version": "2.2", + "is_rfxtrx": False, } assert result["type"] == "create_entry" @@ -179,7 +185,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" @@ -216,7 +223,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" From 1beade900176ec383f71fabb0d596311bf354f54 Mon Sep 17 00:00:00 2001 From: Ronald Pijnacker Date: Sat, 1 Jan 2022 08:51:16 +0000 Subject: [PATCH 03/25] Add test case for RFXtrx variation --- tests/components/dsmr/conftest.py | 33 +++++++++++++++++++++++ tests/components/dsmr/test_config_flow.py | 27 +++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/tests/components/dsmr/conftest.py b/tests/components/dsmr/conftest.py index 9ef6bccfab57cf..5f9804430b5a44 100644 --- a/tests/components/dsmr/conftest.py +++ b/tests/components/dsmr/conftest.py @@ -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, @@ -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) diff --git a/tests/components/dsmr/test_config_flow.py b/tests/components/dsmr/test_config_flow.py index 5d1ecf390a93ea..f710457ad0560f 100644 --- a/tests/components/dsmr/test_config_flow.py +++ b/tests/components/dsmr/test_config_flow.py @@ -103,6 +103,33 @@ async def test_setup_serial(com_mock, hass, dsmr_connection_send_validate_fixtur 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 From 496ccbc8a07557c867401fd6d269a20403ba5e26 Mon Sep 17 00:00:00 2001 From: Ronald Pijnacker Date: Fri, 31 Dec 2021 07:24:12 +0000 Subject: [PATCH 04/25] Read DSMR telegrams over RFXtrx connection --- homeassistant/components/dsmr/config_flow.py | 31 ++++++++++++++++--- homeassistant/components/dsmr/const.py | 1 + homeassistant/components/dsmr/sensor.py | 12 +++++-- .../components/dsmr/translations/en.json | 6 ++-- 4 files changed, 42 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/dsmr/config_flow.py b/homeassistant/components/dsmr/config_flow.py index 587d51d13c7d56..27a63bce95c8a2 100644 --- a/homeassistant/components/dsmr/config_flow.py +++ b/homeassistant/components/dsmr/config_flow.py @@ -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 @@ -22,6 +26,7 @@ from .const import ( CONF_DSMR_VERSION, + CONF_IS_RFXTRX, CONF_SERIAL_ID, CONF_SERIAL_ID_GAS, CONF_TIME_BETWEEN_UPDATE, @@ -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": @@ -77,7 +85,9 @@ 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, @@ -85,7 +95,9 @@ def update_telegram(telegram: dict[str, DSMRObject]) -> None: ) 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, @@ -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 @@ -138,6 +155,7 @@ class DSMRFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): VERSION = 1 _dsmr_version: str | None = None + _is_rfxtrx = False @staticmethod @callback @@ -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, } ) return self.async_show_form( @@ -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( @@ -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) @@ -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( @@ -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] = {} diff --git a/homeassistant/components/dsmr/const.py b/homeassistant/components/dsmr/const.py index 11bab06daba3da..163ddfd088c053 100644 --- a/homeassistant/components/dsmr/const.py +++ b/homeassistant/components/dsmr/const.py @@ -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" diff --git a/homeassistant/components/dsmr/sensor.py b/homeassistant/components/dsmr/sensor.py index 94ae2864905bd1..3da7679ef6766e 100644 --- a/homeassistant/components/dsmr/sensor.py +++ b/homeassistant/components/dsmr/sensor.py @@ -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 @@ -28,6 +32,7 @@ from .const import ( CONF_DSMR_VERSION, + CONF_IS_RFXTRX, CONF_PRECISION, CONF_RECONNECT_INTERVAL, CONF_SERIAL_ID, @@ -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] 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, @@ -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, diff --git a/homeassistant/components/dsmr/translations/en.json b/homeassistant/components/dsmr/translations/en.json index 6f873729bc84bd..9f095166d4d346 100644 --- a/homeassistant/components/dsmr/translations/en.json +++ b/homeassistant/components/dsmr/translations/en.json @@ -15,14 +15,16 @@ "data": { "dsmr_version": "Select DSMR version", "host": "Host", - "port": "Port" + "port": "Port", + "is_rfxtrx": "RFXtrx with integrated P1 reader" }, "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" }, From ebad8bce6c634a283dd95525490a8fb5d5f1b84c Mon Sep 17 00:00:00 2001 From: Ronald Pijnacker Date: Sat, 1 Jan 2022 08:33:57 +0000 Subject: [PATCH 05/25] Fix tests --- tests/components/dsmr/test_config_flow.py | 26 +++++++++++++++-------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/tests/components/dsmr/test_config_flow.py b/tests/components/dsmr/test_config_flow.py index 02c27369f09e87..5d1ecf390a93ea 100644 --- a/tests/components/dsmr/test_config_flow.py +++ b/tests/components/dsmr/test_config_flow.py @@ -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, + }, ) entry_data = { "host": "10.10.0.1", "port": 1234, "dsmr_version": "2.2", + "is_rfxtrx": False, } assert result["type"] == "create_entry" @@ -86,13 +92,11 @@ 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 @@ -122,7 +126,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" @@ -137,6 +142,7 @@ async def test_setup_serial_manual( entry_data = { "port": "/dev/ttyUSB0", "dsmr_version": "2.2", + "is_rfxtrx": False, } assert result["type"] == "create_entry" @@ -179,7 +185,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" @@ -216,7 +223,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" From 5544590de7e0f426a0dbac7a38c28e04212ec33e Mon Sep 17 00:00:00 2001 From: Ronald Pijnacker Date: Sat, 1 Jan 2022 08:51:16 +0000 Subject: [PATCH 06/25] Add test case for RFXtrx variation --- tests/components/dsmr/conftest.py | 33 +++++++++++++++++++++++ tests/components/dsmr/test_config_flow.py | 27 +++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/tests/components/dsmr/conftest.py b/tests/components/dsmr/conftest.py index 9ef6bccfab57cf..5f9804430b5a44 100644 --- a/tests/components/dsmr/conftest.py +++ b/tests/components/dsmr/conftest.py @@ -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, @@ -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) diff --git a/tests/components/dsmr/test_config_flow.py b/tests/components/dsmr/test_config_flow.py index 5d1ecf390a93ea..f710457ad0560f 100644 --- a/tests/components/dsmr/test_config_flow.py +++ b/tests/components/dsmr/test_config_flow.py @@ -103,6 +103,33 @@ async def test_setup_serial(com_mock, hass, dsmr_connection_send_validate_fixtur 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 From 923a4edd6d4721a5781614dd850ff6eea6975de4 Mon Sep 17 00:00:00 2001 From: Ronald Pijnacker Date: Thu, 6 Jan 2022 10:35:28 +0000 Subject: [PATCH 07/25] Revert translation in en.json --- homeassistant/components/dsmr/translations/en.json | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/dsmr/translations/en.json b/homeassistant/components/dsmr/translations/en.json index 9f095166d4d346..6f873729bc84bd 100644 --- a/homeassistant/components/dsmr/translations/en.json +++ b/homeassistant/components/dsmr/translations/en.json @@ -15,16 +15,14 @@ "data": { "dsmr_version": "Select DSMR version", "host": "Host", - "port": "Port", - "is_rfxtrx": "RFXtrx with integrated P1 reader" + "port": "Port" }, "title": "Select connection address" }, "setup_serial": { "data": { "dsmr_version": "Select DSMR version", - "port": "Select device", - "is_rfxtrx": "RFXtrx with integrated P1 reader" + "port": "Select device" }, "title": "Device" }, From bdae65e218f7cccfb44768ea53f10dfe00cd90c0 Mon Sep 17 00:00:00 2001 From: Ronald Pijnacker Date: Thu, 6 Jan 2022 10:36:09 +0000 Subject: [PATCH 08/25] Add UI strings --- homeassistant/components/dsmr/strings.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/dsmr/strings.json b/homeassistant/components/dsmr/strings.json index cc9cd2ae86ae79..ec063ad981b4df 100644 --- a/homeassistant/components/dsmr/strings.json +++ b/homeassistant/components/dsmr/strings.json @@ -11,14 +11,16 @@ "data": { "host": "[%key:common::config_flow::data::host%]", "port": "[%key:common::config_flow::data::port%]", - "dsmr_version": "Select DSMR version" + "dsmr_version": "Select DSMR version", + "is_rfxtrx": "RFXtrx with integrated P1 reader" }, "title": "Select connection address" }, "setup_serial": { "data": { "port": "Select device", - "dsmr_version": "Select DSMR version" + "dsmr_version": "Select DSMR version", + "is_rfxtrx": "RFXtrx with integrated P1 reader" }, "title": "Device" }, From 60785e2f4838d03398a6e1d72638768ab5f27510 Mon Sep 17 00:00:00 2001 From: Ronald Pijnacker Date: Thu, 6 Jan 2022 10:40:06 +0000 Subject: [PATCH 09/25] Make CONF_IS_RFXTRX required --- homeassistant/components/dsmr/config_flow.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/dsmr/config_flow.py b/homeassistant/components/dsmr/config_flow.py index 27a63bce95c8a2..fc162a5c36f411 100644 --- a/homeassistant/components/dsmr/config_flow.py +++ b/homeassistant/components/dsmr/config_flow.py @@ -226,7 +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, + vol.Required(CONF_IS_RFXTRX): bool, } ) return self.async_show_form( @@ -273,7 +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, + vol.Required(CONF_IS_RFXTRX): bool, } ) return self.async_show_form( From 6688ed29aa9dad662c66dd443b5b403d57230029 Mon Sep 17 00:00:00 2001 From: Ronald Pijnacker Date: Thu, 6 Jan 2022 10:48:46 +0000 Subject: [PATCH 10/25] Add sensor test case for RFXtrx --- tests/components/dsmr/conftest.py | 23 +++++++++++++++++++++++ tests/components/dsmr/test_sensor.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/tests/components/dsmr/conftest.py b/tests/components/dsmr/conftest.py index 5f9804430b5a44..f6a0c76dcb48a0 100644 --- a/tests/components/dsmr/conftest.py +++ b/tests/components/dsmr/conftest.py @@ -36,6 +36,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.""" diff --git a/tests/components/dsmr/test_sensor.py b/tests/components/dsmr/test_sensor.py index ff8c2e6a94f594..895aed785beb81 100644 --- a/tests/components/dsmr/test_sensor.py +++ b/tests/components/dsmr/test_sensor.py @@ -603,6 +603,34 @@ async def test_tcp(hass, dsmr_connection_fixture): assert connection_factory.call_args_list[0][0][1] == "1234" +async def test_rfxtrx_tcp(hass, rfxtrx_dsmr_connection_fixture): + """If proper config provided RFXtrx TCP connection should be made.""" + (connection_factory, transport, protocol) = rfxtrx_dsmr_connection_fixture + + entry_data = { + "host": "localhost", + "port": "1234", + "dsmr_version": "2.2", + "is_rfxtrx": True, + "precision": 4, + "reconnect_interval": 30, + "serial_id": "1234", + "serial_id_gas": "5678", + } + + mock_entry = MockConfigEntry( + domain="dsmr", unique_id="/dev/ttyUSB0", data=entry_data + ) + + mock_entry.add_to_hass(hass) + + await hass.config_entries.async_setup(mock_entry.entry_id) + await hass.async_block_till_done() + + assert connection_factory.call_args_list[0][0][0] == "localhost" + assert connection_factory.call_args_list[0][0][1] == "1234" + + async def test_connection_errors_retry(hass, dsmr_connection_fixture): """Connection should be retried on error during setup.""" (connection_factory, transport, protocol) = dsmr_connection_fixture From 2f4d380367bb376b5032df6cd25ed925cf082003 Mon Sep 17 00:00:00 2001 From: Ronald Pijnacker Date: Thu, 6 Jan 2022 10:50:27 +0000 Subject: [PATCH 11/25] Review improvement --- homeassistant/components/dsmr/sensor.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/homeassistant/components/dsmr/sensor.py b/homeassistant/components/dsmr/sensor.py index 3da7679ef6766e..a41b6413d1f17a 100644 --- a/homeassistant/components/dsmr/sensor.py +++ b/homeassistant/components/dsmr/sensor.py @@ -82,9 +82,7 @@ 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] + is_rfxtrx = entry.data.get(CONF_IS_RFXTRX) if CONF_HOST in entry.data: reader_factory = partial( create_tcp_dsmr_reader if not is_rfxtrx else create_rfxtrx_tcp_dsmr_reader, From f19ac66a4946a89b1edca835edbd7d00ebd472b0 Mon Sep 17 00:00:00 2001 From: Ronald Pijnacker Date: Wed, 12 Jan 2022 15:57:00 +0000 Subject: [PATCH 12/25] Fix merged-in test cases --- tests/components/dsmr/test_config_flow.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/components/dsmr/test_config_flow.py b/tests/components/dsmr/test_config_flow.py index ee82a1d10ed71c..402f0295ef11dd 100644 --- a/tests/components/dsmr/test_config_flow.py +++ b/tests/components/dsmr/test_config_flow.py @@ -154,12 +154,14 @@ async def test_setup_5L(com_mock, 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"], {"port": port.device, "dsmr_version": "5L"} + result["flow_id"], + {"port": port.device, "dsmr_version": "5L", "is_rfxtrx": False}, ) entry_data = { "port": port.device, "dsmr_version": "5L", + "is_rfxtrx": False, "serial_id": "12345678", "serial_id_gas": "123456789", } @@ -193,12 +195,14 @@ async def test_setup_Q3D(com_mock, 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"], {"port": port.device, "dsmr_version": "Q3D"} + result["flow_id"], + {"port": port.device, "dsmr_version": "Q3D", "is_rfxtrx": False}, ) entry_data = { "port": port.device, "dsmr_version": "Q3D", + "is_rfxtrx": False, "serial_id": "12345678", "serial_id_gas": None, } From 3aa5e7fe5892938d414ad823ddd82d0ca7b87d2b Mon Sep 17 00:00:00 2001 From: Ronald Pijnacker Date: Sat, 22 Jan 2022 07:09:21 +0000 Subject: [PATCH 13/25] Auto-detect RFXtrx by retrying --- homeassistant/components/dsmr/config_flow.py | 18 ++++----- tests/components/dsmr/conftest.py | 31 +-------------- tests/components/dsmr/test_config_flow.py | 40 +++----------------- 3 files changed, 16 insertions(+), 73 deletions(-) diff --git a/homeassistant/components/dsmr/config_flow.py b/homeassistant/components/dsmr/config_flow.py index efb2751b7e7d14..82b3c964c69cde 100644 --- a/homeassistant/components/dsmr/config_flow.py +++ b/homeassistant/components/dsmr/config_flow.py @@ -125,14 +125,14 @@ 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], is_rfxtrx: bool ) -> 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], - data[CONF_IS_RFXTRX], + is_rfxtrx, ) if not await conn.validate_connect(hass): @@ -228,7 +228,6 @@ 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.Required(CONF_IS_RFXTRX): bool, } ) return self.async_show_form( @@ -246,7 +245,6 @@ 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( @@ -256,7 +254,6 @@ 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) @@ -275,7 +272,6 @@ 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.Required(CONF_IS_RFXTRX): bool, } ) return self.async_show_form( @@ -292,7 +288,6 @@ 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] = {} @@ -313,9 +308,14 @@ async def async_validate_dsmr( data = input_data try: - info = await _validate_dsmr_connection(self.hass, data) + try: + is_rfxtrx = False + info = await _validate_dsmr_connection(self.hass, data, is_rfxtrx) + except CannotCommunicate: + is_rfxtrx = True + info = await _validate_dsmr_connection(self.hass, data, is_rfxtrx) - data = {**data, **info} + data = {**data, **info, CONF_IS_RFXTRX: is_rfxtrx} if info[CONF_SERIAL_ID]: await self.async_set_unique_id(info[CONF_SERIAL_ID]) diff --git a/tests/components/dsmr/conftest.py b/tests/components/dsmr/conftest.py index 4364be1c2bebeb..d379589e942cf4 100644 --- a/tests/components/dsmr/conftest.py +++ b/tests/components/dsmr/conftest.py @@ -117,36 +117,7 @@ async def wait_closed(): ), patch( "homeassistant.components.dsmr.config_flow.create_tcp_dsmr_reader", 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( + ), patch( "homeassistant.components.dsmr.config_flow.create_rfxtrx_dsmr_reader", connection_factory, ): diff --git a/tests/components/dsmr/test_config_flow.py b/tests/components/dsmr/test_config_flow.py index 402f0295ef11dd..ec3c037f28fb1d 100644 --- a/tests/components/dsmr/test_config_flow.py +++ b/tests/components/dsmr/test_config_flow.py @@ -52,7 +52,6 @@ async def test_setup_network(hass, dsmr_connection_send_validate_fixture): "host": "10.10.0.1", "port": 1234, "dsmr_version": "2.2", - "is_rfxtrx": False, }, ) @@ -93,7 +92,7 @@ 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", "is_rfxtrx": False}, + {"port": port.device, "dsmr_version": "2.2"}, ) entry_data = {"port": port.device, "dsmr_version": "2.2", "is_rfxtrx": False} @@ -103,33 +102,6 @@ async def test_setup_serial(com_mock, hass, dsmr_connection_send_validate_fixtur 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_5L(com_mock, hass, dsmr_connection_send_validate_fixture): """Test we can setup serial.""" @@ -155,7 +127,7 @@ async def test_setup_5L(com_mock, 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"], - {"port": port.device, "dsmr_version": "5L", "is_rfxtrx": False}, + {"port": port.device, "dsmr_version": "5L"}, ) entry_data = { @@ -196,7 +168,7 @@ async def test_setup_Q3D(com_mock, 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"], - {"port": port.device, "dsmr_version": "Q3D", "is_rfxtrx": False}, + {"port": port.device, "dsmr_version": "Q3D"}, ) entry_data = { @@ -236,7 +208,7 @@ async def test_setup_serial_manual( result = await hass.config_entries.flow.async_configure( result["flow_id"], - {"port": "Enter Manually", "dsmr_version": "2.2", "is_rfxtrx": False}, + {"port": "Enter Manually", "dsmr_version": "2.2"}, ) assert result["type"] == "form" @@ -295,7 +267,7 @@ async def test_setup_serial_fail(com_mock, hass, dsmr_connection_send_validate_f ): result = await hass.config_entries.flow.async_configure( result["flow_id"], - {"port": port.device, "dsmr_version": "2.2", "is_rfxtrx": False}, + {"port": port.device, "dsmr_version": "2.2"}, ) assert result["type"] == "form" @@ -333,7 +305,7 @@ async def test_setup_serial_wrong_telegram( result = await hass.config_entries.flow.async_configure( result["flow_id"], - {"port": port.device, "dsmr_version": "2.2", "is_rfxtrx": False}, + {"port": port.device, "dsmr_version": "2.2"}, ) assert result["type"] == "form" From 225de650a1635f23a88ed48b257562d76d575a9d Mon Sep 17 00:00:00 2001 From: Ronald Pijnacker Date: Sat, 22 Jan 2022 07:12:15 +0000 Subject: [PATCH 14/25] Remove unused string resource --- homeassistant/components/dsmr/strings.json | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/dsmr/strings.json b/homeassistant/components/dsmr/strings.json index ec063ad981b4df..cc9cd2ae86ae79 100644 --- a/homeassistant/components/dsmr/strings.json +++ b/homeassistant/components/dsmr/strings.json @@ -11,16 +11,14 @@ "data": { "host": "[%key:common::config_flow::data::host%]", "port": "[%key:common::config_flow::data::port%]", - "dsmr_version": "Select DSMR version", - "is_rfxtrx": "RFXtrx with integrated P1 reader" + "dsmr_version": "Select DSMR version" }, "title": "Select connection address" }, "setup_serial": { "data": { "port": "Select device", - "dsmr_version": "Select DSMR version", - "is_rfxtrx": "RFXtrx with integrated P1 reader" + "dsmr_version": "Select DSMR version" }, "title": "Device" }, From 2a89bed7829dd0e5d34a555788f68a3debf35e7a Mon Sep 17 00:00:00 2001 From: Ronald Pijnacker Date: Sat, 22 Jan 2022 21:01:00 +0000 Subject: [PATCH 15/25] As requested in a review comment --- homeassistant/components/dsmr/config_flow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/dsmr/config_flow.py b/homeassistant/components/dsmr/config_flow.py index 82b3c964c69cde..751c6685713229 100644 --- a/homeassistant/components/dsmr/config_flow.py +++ b/homeassistant/components/dsmr/config_flow.py @@ -308,8 +308,8 @@ async def async_validate_dsmr( data = input_data try: + is_rfxtrx = False try: - is_rfxtrx = False info = await _validate_dsmr_connection(self.hass, data, is_rfxtrx) except CannotCommunicate: is_rfxtrx = True From 2e82c044f800e0deb9981f8ef7091081750d5fe9 Mon Sep 17 00:00:00 2001 From: Ronald Pijnacker Date: Sun, 23 Jan 2022 19:05:14 +0000 Subject: [PATCH 16/25] Change `is_rfxtrx` with `protocol` parameter --- homeassistant/components/dsmr/config_flow.py | 27 ++++++++++---------- homeassistant/components/dsmr/const.py | 5 +++- homeassistant/components/dsmr/sensor.py | 13 +++++++--- tests/components/dsmr/test_config_flow.py | 14 ++++++---- tests/components/dsmr/test_sensor.py | 3 ++- 5 files changed, 38 insertions(+), 24 deletions(-) diff --git a/homeassistant/components/dsmr/config_flow.py b/homeassistant/components/dsmr/config_flow.py index 751c6685713229..146f1754f55acd 100644 --- a/homeassistant/components/dsmr/config_flow.py +++ b/homeassistant/components/dsmr/config_flow.py @@ -26,14 +26,16 @@ from .const import ( CONF_DSMR_VERSION, - CONF_IS_RFXTRX, + CONF_PROTOCOL, CONF_SERIAL_ID, CONF_SERIAL_ID_GAS, CONF_TIME_BETWEEN_UPDATE, DEFAULT_TIME_BETWEEN_UPDATE, DOMAIN, + DSMR_PROTOCOL, DSMR_VERSIONS, LOGGER, + RFXTRXDSMR_PROTOCOL, ) CONF_MANUAL_PATH = "Enter Manually" @@ -43,13 +45,13 @@ 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, is_rfxtrx: bool = False + 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._is_rfxtrx = is_rfxtrx + self._protocol = protocol self._telegram: dict[str, DSMRObject] = {} self._equipment_identifier = obis_ref.EQUIPMENT_IDENTIFIER if dsmr_version == "5L": @@ -88,7 +90,7 @@ def update_telegram(telegram: dict[str, DSMRObject]) -> None: if self._host is None: reader_factory = partial( create_dsmr_reader - if not self._is_rfxtrx + if self._protocol == DSMR_PROTOCOL else create_rfxtrx_dsmr_reader, self._port, self._dsmr_version, @@ -98,7 +100,7 @@ def update_telegram(telegram: dict[str, DSMRObject]) -> None: else: reader_factory = partial( create_tcp_dsmr_reader - if not self._is_rfxtrx + if self._protocol == DSMR_PROTOCOL else create_rfxtrx_tcp_dsmr_reader, self._host, self._port, @@ -125,14 +127,14 @@ def update_telegram(telegram: dict[str, DSMRObject]) -> None: async def _validate_dsmr_connection( - hass: core.HomeAssistant, data: dict[str, Any], is_rfxtrx: bool + 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], - is_rfxtrx, + protocol, ) if not await conn.validate_connect(hass): @@ -157,7 +159,6 @@ class DSMRFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): VERSION = 1 _dsmr_version: str | None = None - _is_rfxtrx = False @staticmethod @callback @@ -308,14 +309,14 @@ async def async_validate_dsmr( data = input_data try: - is_rfxtrx = False + protocol = DSMR_PROTOCOL try: - info = await _validate_dsmr_connection(self.hass, data, is_rfxtrx) + info = await _validate_dsmr_connection(self.hass, data, protocol) except CannotCommunicate: - is_rfxtrx = True - info = await _validate_dsmr_connection(self.hass, data, is_rfxtrx) + protocol = RFXTRXDSMR_PROTOCOL + info = await _validate_dsmr_connection(self.hass, data, protocol) - data = {**data, **info, CONF_IS_RFXTRX: is_rfxtrx} + data = {**data, **info, CONF_PROTOCOL: protocol} if info[CONF_SERIAL_ID]: await self.async_set_unique_id(info[CONF_SERIAL_ID]) diff --git a/homeassistant/components/dsmr/const.py b/homeassistant/components/dsmr/const.py index 5d98935082df3d..447b39e3dd439c 100644 --- a/homeassistant/components/dsmr/const.py +++ b/homeassistant/components/dsmr/const.py @@ -17,7 +17,7 @@ PLATFORMS = [Platform.SENSOR] CONF_DSMR_VERSION = "dsmr_version" -CONF_IS_RFXTRX = "is_rfxtrx" +CONF_PROTOCOL = "protocol" CONF_RECONNECT_INTERVAL = "reconnect_interval" CONF_PRECISION = "precision" CONF_TIME_BETWEEN_UPDATE = "time_between_update" @@ -38,6 +38,9 @@ DSMR_VERSIONS = {"2.2", "4", "5", "5B", "5L", "5S", "Q3D"} +DSMR_PROTOCOL = "dsmr_protocol" +RFXTRXDSMR_PROTOCOL = "rfxtrxdsmr_protocol" + SENSORS: tuple[DSMRSensorEntityDescription, ...] = ( DSMRSensorEntityDescription( key=obis_references.CURRENT_ELECTRICITY_USAGE, diff --git a/homeassistant/components/dsmr/sensor.py b/homeassistant/components/dsmr/sensor.py index a41b6413d1f17a..bdf255f239b79d 100644 --- a/homeassistant/components/dsmr/sensor.py +++ b/homeassistant/components/dsmr/sensor.py @@ -32,8 +32,8 @@ from .const import ( CONF_DSMR_VERSION, - CONF_IS_RFXTRX, CONF_PRECISION, + CONF_PROTOCOL, CONF_RECONNECT_INTERVAL, CONF_SERIAL_ID, CONF_SERIAL_ID_GAS, @@ -45,6 +45,7 @@ DEVICE_NAME_ENERGY, DEVICE_NAME_GAS, DOMAIN, + DSMR_PROTOCOL, LOGGER, SENSORS, ) @@ -82,10 +83,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 = entry.data.get(CONF_IS_RFXTRX) + protocol = entry.data.get(CONF_PROTOCOL, DSMR_PROTOCOL) if CONF_HOST in entry.data: reader_factory = partial( - create_tcp_dsmr_reader if not is_rfxtrx else create_rfxtrx_tcp_dsmr_reader, + create_tcp_dsmr_reader + if protocol == DSMR_PROTOCOL + else create_rfxtrx_tcp_dsmr_reader, entry.data[CONF_HOST], entry.data[CONF_PORT], dsmr_version, @@ -95,7 +98,9 @@ def update_entities_telegram(telegram: dict[str, DSMRObject]) -> None: ) else: reader_factory = partial( - create_dsmr_reader if not is_rfxtrx else create_rfxtrx_dsmr_reader, + create_dsmr_reader + if protocol == DSMR_PROTOCOL + else create_rfxtrx_dsmr_reader, entry.data[CONF_PORT], dsmr_version, update_entities_telegram, diff --git a/tests/components/dsmr/test_config_flow.py b/tests/components/dsmr/test_config_flow.py index ec3c037f28fb1d..bb9d0ef24b10aa 100644 --- a/tests/components/dsmr/test_config_flow.py +++ b/tests/components/dsmr/test_config_flow.py @@ -59,7 +59,7 @@ async def test_setup_network(hass, dsmr_connection_send_validate_fixture): "host": "10.10.0.1", "port": 1234, "dsmr_version": "2.2", - "is_rfxtrx": False, + "protocol": "dsmr_protocol", } assert result["type"] == "create_entry" @@ -95,7 +95,11 @@ async def test_setup_serial(com_mock, hass, dsmr_connection_send_validate_fixtur {"port": port.device, "dsmr_version": "2.2"}, ) - entry_data = {"port": port.device, "dsmr_version": "2.2", "is_rfxtrx": False} + entry_data = { + "port": port.device, + "dsmr_version": "2.2", + "protocol": "dsmr_protocol", + } assert result["type"] == "create_entry" assert result["title"] == port.device @@ -133,7 +137,7 @@ async def test_setup_5L(com_mock, hass, dsmr_connection_send_validate_fixture): entry_data = { "port": port.device, "dsmr_version": "5L", - "is_rfxtrx": False, + "protocol": "dsmr_protocol", "serial_id": "12345678", "serial_id_gas": "123456789", } @@ -174,7 +178,7 @@ async def test_setup_Q3D(com_mock, hass, dsmr_connection_send_validate_fixture): entry_data = { "port": port.device, "dsmr_version": "Q3D", - "is_rfxtrx": False, + "protocol": "dsmr_protocol", "serial_id": "12345678", "serial_id_gas": None, } @@ -223,7 +227,7 @@ async def test_setup_serial_manual( entry_data = { "port": "/dev/ttyUSB0", "dsmr_version": "2.2", - "is_rfxtrx": False, + "protocol": "dsmr_protocol", } assert result["type"] == "create_entry" diff --git a/tests/components/dsmr/test_sensor.py b/tests/components/dsmr/test_sensor.py index 401590b96d53d8..fa4a53801a1a42 100644 --- a/tests/components/dsmr/test_sensor.py +++ b/tests/components/dsmr/test_sensor.py @@ -658,6 +658,7 @@ async def test_tcp(hass, dsmr_connection_fixture): "host": "localhost", "port": "1234", "dsmr_version": "2.2", + "protocol": "dsmr_protocol", "precision": 4, "reconnect_interval": 30, "serial_id": "1234", @@ -685,7 +686,7 @@ async def test_rfxtrx_tcp(hass, rfxtrx_dsmr_connection_fixture): "host": "localhost", "port": "1234", "dsmr_version": "2.2", - "is_rfxtrx": True, + "protocol": "rfxtrxdsmr_protocol", "precision": 4, "reconnect_interval": 30, "serial_id": "1234", From fa76a68239b0e6b1ec8c06df85853ec2486990aa Mon Sep 17 00:00:00 2001 From: Ronald Pijnacker Date: Tue, 25 Jan 2022 11:07:33 +0000 Subject: [PATCH 17/25] More review comments --- homeassistant/components/dsmr/config_flow.py | 6 +++--- homeassistant/components/dsmr/const.py | 2 +- homeassistant/components/dsmr/sensor.py | 16 ++++++++++------ tests/components/dsmr/test_sensor.py | 2 +- 4 files changed, 15 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/dsmr/config_flow.py b/homeassistant/components/dsmr/config_flow.py index 146f1754f55acd..e0b2ee4761f3c2 100644 --- a/homeassistant/components/dsmr/config_flow.py +++ b/homeassistant/components/dsmr/config_flow.py @@ -35,7 +35,7 @@ DSMR_PROTOCOL, DSMR_VERSIONS, LOGGER, - RFXTRXDSMR_PROTOCOL, + RFXTRX_DSMR_PROTOCOL, ) CONF_MANUAL_PATH = "Enter Manually" @@ -308,12 +308,12 @@ async def async_validate_dsmr( """Validate dsmr connection and create data.""" data = input_data + protocol = DSMR_PROTOCOL try: - protocol = DSMR_PROTOCOL try: info = await _validate_dsmr_connection(self.hass, data, protocol) except CannotCommunicate: - protocol = RFXTRXDSMR_PROTOCOL + protocol = RFXTRX_DSMR_PROTOCOL info = await _validate_dsmr_connection(self.hass, data, protocol) data = {**data, **info, CONF_PROTOCOL: protocol} diff --git a/homeassistant/components/dsmr/const.py b/homeassistant/components/dsmr/const.py index 447b39e3dd439c..6fca6b2f82f703 100644 --- a/homeassistant/components/dsmr/const.py +++ b/homeassistant/components/dsmr/const.py @@ -39,7 +39,7 @@ DSMR_VERSIONS = {"2.2", "4", "5", "5B", "5L", "5S", "Q3D"} DSMR_PROTOCOL = "dsmr_protocol" -RFXTRXDSMR_PROTOCOL = "rfxtrxdsmr_protocol" +RFXTRX_DSMR_PROTOCOL = "rfxtrx_dsmr_protocol" SENSORS: tuple[DSMRSensorEntityDescription, ...] = ( DSMRSensorEntityDescription( diff --git a/homeassistant/components/dsmr/sensor.py b/homeassistant/components/dsmr/sensor.py index bdf255f239b79d..369019091903a4 100644 --- a/homeassistant/components/dsmr/sensor.py +++ b/homeassistant/components/dsmr/sensor.py @@ -85,10 +85,12 @@ def update_entities_telegram(telegram: dict[str, DSMRObject]) -> None: # 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 - if protocol == DSMR_PROTOCOL - else create_rfxtrx_tcp_dsmr_reader, + create_reader, entry.data[CONF_HOST], entry.data[CONF_PORT], dsmr_version, @@ -97,10 +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 - if protocol == DSMR_PROTOCOL - else create_rfxtrx_dsmr_reader, + create_reader, entry.data[CONF_PORT], dsmr_version, update_entities_telegram, diff --git a/tests/components/dsmr/test_sensor.py b/tests/components/dsmr/test_sensor.py index fa4a53801a1a42..93dd78034ccd1a 100644 --- a/tests/components/dsmr/test_sensor.py +++ b/tests/components/dsmr/test_sensor.py @@ -686,7 +686,7 @@ async def test_rfxtrx_tcp(hass, rfxtrx_dsmr_connection_fixture): "host": "localhost", "port": "1234", "dsmr_version": "2.2", - "protocol": "rfxtrxdsmr_protocol", + "protocol": "rfxtrx_dsmr_protocol", "precision": 4, "reconnect_interval": 30, "serial_id": "1234", From 403aaf2f08956a0328e46b7d73a0ff651980eb0b Mon Sep 17 00:00:00 2001 From: Ronald Pijnacker Date: Tue, 25 Jan 2022 11:14:31 +0000 Subject: [PATCH 18/25] More review comments --- homeassistant/components/dsmr/config_flow.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/dsmr/config_flow.py b/homeassistant/components/dsmr/config_flow.py index e0b2ee4761f3c2..002280283cbed1 100644 --- a/homeassistant/components/dsmr/config_flow.py +++ b/homeassistant/components/dsmr/config_flow.py @@ -88,20 +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 - if self._protocol == DSMR_PROTOCOL - else create_rfxtrx_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 - if self._protocol == DSMR_PROTOCOL - else create_rfxtrx_tcp_dsmr_reader, + create_reader, self._host, self._port, self._dsmr_version, From bc568390c5d69e9a58e9c16c723f29a4b118d621 Mon Sep 17 00:00:00 2001 From: Ronald Pijnacker Date: Tue, 25 Jan 2022 21:11:29 +0000 Subject: [PATCH 19/25] Add unit test for RFXtrx --- tests/components/dsmr/conftest.py | 39 +++++++- tests/components/dsmr/test_config_flow.py | 114 +++++++++++++++++++++- 2 files changed, 149 insertions(+), 4 deletions(-) diff --git a/tests/components/dsmr/conftest.py b/tests/components/dsmr/conftest.py index d379589e942cf4..8c94c756edc261 100644 --- a/tests/components/dsmr/conftest.py +++ b/tests/components/dsmr/conftest.py @@ -117,8 +117,45 @@ async def wait_closed(): ), patch( "homeassistant.components.dsmr.config_flow.create_tcp_dsmr_reader", connection_factory, - ), patch( + ): + 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) diff --git a/tests/components/dsmr/test_config_flow.py b/tests/components/dsmr/test_config_flow.py index bb9d0ef24b10aa..d6c40ff3fd68bf 100644 --- a/tests/components/dsmr/test_config_flow.py +++ b/tests/components/dsmr/test_config_flow.py @@ -67,6 +67,56 @@ async def test_setup_network(hass, dsmr_connection_send_validate_fixture): assert result["data"] == {**entry_data, **SERIAL_DATA} +async def test_setup_network_rfxtrx( + hass, + dsmr_connection_send_validate_fixture, + rfxtrx_dsmr_connection_send_validate_fixture, +): + """Test we can setup network.""" + (connection_factory, transport, protocol) = dsmr_connection_send_validate_fixture + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + assert result["type"] == "form" + assert result["step_id"] == "user" + assert result["errors"] is None + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + {"type": "Network"}, + ) + + assert result["type"] == "form" + assert result["step_id"] == "setup_network" + assert result["errors"] == {} + + # set-up DSMRProtocol to yield no valid telegram, this will retry with RFXtrxDSMRProtocol + protocol.telegram = {} + + 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", + }, + ) + + entry_data = { + "host": "10.10.0.1", + "port": 1234, + "dsmr_version": "2.2", + "protocol": "rfxtrx_dsmr_protocol", + } + + assert result["type"] == "create_entry" + assert result["title"] == "10.10.0.1:1234" + assert result["data"] == {**entry_data, **SERIAL_DATA} + + @patch("serial.tools.list_ports.comports", return_value=[com_port()]) async def test_setup_serial(com_mock, hass, dsmr_connection_send_validate_fixture): """Test we can setup serial.""" @@ -106,6 +156,55 @@ async def test_setup_serial(com_mock, hass, dsmr_connection_send_validate_fixtur 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, + dsmr_connection_send_validate_fixture, + rfxtrx_dsmr_connection_send_validate_fixture, +): + """Test we can setup serial.""" + (connection_factory, transport, protocol) = dsmr_connection_send_validate_fixture + + port = com_port() + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + assert result["type"] == "form" + assert result["step_id"] == "user" + assert result["errors"] is None + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + {"type": "Serial"}, + ) + + assert result["type"] == "form" + assert result["step_id"] == "setup_serial" + assert result["errors"] == {} + + # set-up DSMRProtocol to yield no valid telegram, this will retry with RFXtrxDSMRProtocol + protocol.telegram = {} + + 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"}, + ) + + entry_data = { + "port": port.device, + "dsmr_version": "2.2", + "protocol": "rfxtrx_dsmr_protocol", + } + + 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_5L(com_mock, hass, dsmr_connection_send_validate_fixture): """Test we can setup serial.""" @@ -281,10 +380,18 @@ async def test_setup_serial_fail(com_mock, hass, dsmr_connection_send_validate_f @patch("serial.tools.list_ports.comports", return_value=[com_port()]) async def test_setup_serial_wrong_telegram( - com_mock, hass, dsmr_connection_send_validate_fixture + com_mock, + hass, + dsmr_connection_send_validate_fixture, + rfxtrx_dsmr_connection_send_validate_fixture, ): """Test failed telegram data.""" (connection_factory, transport, protocol) = dsmr_connection_send_validate_fixture + ( + rfxtrx_connection_factory, + transport, + rfxtrx_protocol, + ) = rfxtrx_dsmr_connection_send_validate_fixture port = com_port() @@ -292,8 +399,6 @@ async def test_setup_serial_wrong_telegram( DOMAIN, context={"source": config_entries.SOURCE_USER} ) - protocol.telegram = {} - assert result["type"] == "form" assert result["step_id"] == "user" assert result["errors"] is None @@ -307,6 +412,9 @@ async def test_setup_serial_wrong_telegram( assert result["step_id"] == "setup_serial" assert result["errors"] == {} + protocol.telegram = {} + rfxtrx_protocol.telegram = {} + result = await hass.config_entries.flow.async_configure( result["flow_id"], {"port": port.device, "dsmr_version": "2.2"}, From 713c03a58abbc259d42260eec2f500c16a983783 Mon Sep 17 00:00:00 2001 From: Ronald Pijnacker Date: Sun, 30 Jan 2022 08:50:38 +0000 Subject: [PATCH 20/25] Clean-up unused functio --- homeassistant/components/dsmr/config_flow.py | 30 -------------------- 1 file changed, 30 deletions(-) diff --git a/homeassistant/components/dsmr/config_flow.py b/homeassistant/components/dsmr/config_flow.py index 002280283cbed1..4d296a14c44704 100644 --- a/homeassistant/components/dsmr/config_flow.py +++ b/homeassistant/components/dsmr/config_flow.py @@ -170,36 +170,6 @@ def async_get_options_flow(config_entry: ConfigEntry) -> DSMROptionFlowHandler: """Get the options flow for this handler.""" return DSMROptionFlowHandler(config_entry) - def _abort_if_host_port_configured( - self, - port: str, - host: str | None = None, - updates: dict[Any, Any] | None = None, - reload_on_update: bool = True, - ) -> FlowResult | None: - """Test if host and port are already configured.""" - for entry in self._async_current_entries(): - if entry.data.get(CONF_HOST) == host and entry.data[CONF_PORT] == port: - if updates is not None: - changed = self.hass.config_entries.async_update_entry( - entry, data={**entry.data, **updates} - ) - if ( - changed - and reload_on_update - and entry.state - in ( - config_entries.ConfigEntryState.LOADED, - config_entries.ConfigEntryState.SETUP_RETRY, - ) - ): - self.hass.async_create_task( - self.hass.config_entries.async_reload(entry.entry_id) - ) - return self.async_abort(reason="already_configured") - - return None - async def async_step_user( self, user_input: dict[str, Any] | None = None ) -> FlowResult: From f136158e22063eb8b31bed8e2a00704a5abb24a0 Mon Sep 17 00:00:00 2001 From: Ronald Pijnacker Date: Sun, 20 Mar 2022 20:25:12 +0000 Subject: [PATCH 21/25] Improve code cohesion --- homeassistant/components/dsmr/config_flow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/dsmr/config_flow.py b/homeassistant/components/dsmr/config_flow.py index 4d296a14c44704..6152a3756e3a2c 100644 --- a/homeassistant/components/dsmr/config_flow.py +++ b/homeassistant/components/dsmr/config_flow.py @@ -282,9 +282,9 @@ async def async_validate_dsmr( """Validate dsmr connection and create data.""" data = input_data - protocol = DSMR_PROTOCOL try: try: + protocol = DSMR_PROTOCOL info = await _validate_dsmr_connection(self.hass, data, protocol) except CannotCommunicate: protocol = RFXTRX_DSMR_PROTOCOL From df56a4a5dceffbbc78c4da396eff5114aadbde8e Mon Sep 17 00:00:00 2001 From: Ronald Pijnacker Date: Sun, 20 Mar 2022 20:28:04 +0000 Subject: [PATCH 22/25] Fix failing unit test --- tests/components/dsmr/test_config_flow.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/tests/components/dsmr/test_config_flow.py b/tests/components/dsmr/test_config_flow.py index e953b2992d62cd..0fa665c33de37d 100644 --- a/tests/components/dsmr/test_config_flow.py +++ b/tests/components/dsmr/test_config_flow.py @@ -277,6 +277,7 @@ async def test_setup_5S(com_mock, hass, dsmr_connection_send_validate_fixture): entry_data = { "port": port.device, "dsmr_version": "5S", + "protocol": "dsmr_protocol", "serial_id": None, "serial_id_gas": None, } @@ -420,10 +421,18 @@ async def test_setup_serial_fail(com_mock, hass, dsmr_connection_send_validate_f @patch("serial.tools.list_ports.comports", return_value=[com_port()]) async def test_setup_serial_timeout( - com_mock, hass, dsmr_connection_send_validate_fixture + com_mock, + hass, + dsmr_connection_send_validate_fixture, + rfxtrx_dsmr_connection_send_validate_fixture, ): """Test failed serial connection.""" (connection_factory, transport, protocol) = dsmr_connection_send_validate_fixture + ( + connection_factory, + transport, + rfxtrx_protocol, + ) = rfxtrx_dsmr_connection_send_validate_fixture port = com_port() @@ -437,6 +446,12 @@ async def test_setup_serial_timeout( ) protocol.wait_closed = first_timeout_wait_closed + first_timeout_wait_closed = AsyncMock( + return_value=True, + side_effect=chain([asyncio.TimeoutError], repeat(DEFAULT)), + ) + rfxtrx_protocol.wait_closed = first_timeout_wait_closed + assert result["type"] == "form" assert result["step_id"] == "user" assert result["errors"] is None From 234bdf66e0bae3885cfaf8ca4adcd440f0a566fa Mon Sep 17 00:00:00 2001 From: rhpijnacker Date: Mon, 21 Mar 2022 07:14:41 +0100 Subject: [PATCH 23/25] Update tests/components/dsmr/test_config_flow.py Co-authored-by: Martin Hjelmare --- tests/components/dsmr/test_config_flow.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/components/dsmr/test_config_flow.py b/tests/components/dsmr/test_config_flow.py index 0fa665c33de37d..677869b8edd534 100644 --- a/tests/components/dsmr/test_config_flow.py +++ b/tests/components/dsmr/test_config_flow.py @@ -105,6 +105,7 @@ async def test_setup_network_rfxtrx( "dsmr_version": "2.2", }, ) + await hass.async_block_till_done() entry_data = { "host": "10.10.0.1", From a1605194c58c3cb844caea66e57b3685ceb07b84 Mon Sep 17 00:00:00 2001 From: Ronald Pijnacker Date: Sat, 26 Mar 2022 15:07:19 +0000 Subject: [PATCH 24/25] Block until all tasks are finished --- tests/components/dsmr/test_config_flow.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/components/dsmr/test_config_flow.py b/tests/components/dsmr/test_config_flow.py index 677869b8edd534..f1b5f9efb72afb 100644 --- a/tests/components/dsmr/test_config_flow.py +++ b/tests/components/dsmr/test_config_flow.py @@ -55,6 +55,7 @@ async def test_setup_network(hass, dsmr_connection_send_validate_fixture): "dsmr_version": "2.2", }, ) + await hass.async_block_till_done() entry_data = { "host": "10.10.0.1", @@ -146,6 +147,7 @@ async def test_setup_serial(com_mock, hass, dsmr_connection_send_validate_fixtur result["flow_id"], {"port": port.device, "dsmr_version": "2.2"}, ) + await hass.async_block_till_done() entry_data = { "port": port.device, @@ -195,6 +197,7 @@ async def test_setup_serial_rfxtrx( result["flow_id"], {"port": port.device, "dsmr_version": "2.2"}, ) + await hass.async_block_till_done() entry_data = { "port": port.device, @@ -234,6 +237,7 @@ async def test_setup_5L(com_mock, hass, dsmr_connection_send_validate_fixture): result["flow_id"], {"port": port.device, "dsmr_version": "5L"}, ) + await hass.async_block_till_done() entry_data = { "port": port.device, @@ -274,6 +278,7 @@ async def test_setup_5S(com_mock, hass, dsmr_connection_send_validate_fixture): result = await hass.config_entries.flow.async_configure( result["flow_id"], {"port": port.device, "dsmr_version": "5S"} ) + await hass.async_block_till_done() entry_data = { "port": port.device, @@ -315,6 +320,7 @@ async def test_setup_Q3D(com_mock, hass, dsmr_connection_send_validate_fixture): result["flow_id"], {"port": port.device, "dsmr_version": "Q3D"}, ) + await hass.async_block_till_done() entry_data = { "port": port.device, @@ -364,6 +370,7 @@ async def test_setup_serial_manual( result = await hass.config_entries.flow.async_configure( result["flow_id"], {"port": "/dev/ttyUSB0"} ) + await hass.async_block_till_done() entry_data = { "port": "/dev/ttyUSB0", @@ -414,6 +421,7 @@ async def test_setup_serial_fail(com_mock, hass, dsmr_connection_send_validate_f result["flow_id"], {"port": port.device, "dsmr_version": "2.2"}, ) + await hass.async_block_till_done() assert result["type"] == "form" assert result["step_id"] == "setup_serial" @@ -470,6 +478,7 @@ async def test_setup_serial_timeout( result = await hass.config_entries.flow.async_configure( result["flow_id"], {"port": port.device, "dsmr_version": "2.2"} ) + await hass.async_block_till_done() assert result["type"] == "form" assert result["step_id"] == "setup_serial" @@ -517,6 +526,7 @@ async def test_setup_serial_wrong_telegram( result["flow_id"], {"port": port.device, "dsmr_version": "2.2"}, ) + await hass.async_block_till_done() assert result["type"] == "form" assert result["step_id"] == "setup_serial" From ddfe2bda80a4daab4b3818ba3ab3d9ff742702b2 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Sat, 26 Mar 2022 16:38:40 +0100 Subject: [PATCH 25/25] Clean up --- tests/components/dsmr/test_config_flow.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/components/dsmr/test_config_flow.py b/tests/components/dsmr/test_config_flow.py index f1b5f9efb72afb..ddec7bda888a92 100644 --- a/tests/components/dsmr/test_config_flow.py +++ b/tests/components/dsmr/test_config_flow.py @@ -421,7 +421,6 @@ async def test_setup_serial_fail(com_mock, hass, dsmr_connection_send_validate_f result["flow_id"], {"port": port.device, "dsmr_version": "2.2"}, ) - await hass.async_block_till_done() assert result["type"] == "form" assert result["step_id"] == "setup_serial" @@ -478,7 +477,6 @@ async def test_setup_serial_timeout( result = await hass.config_entries.flow.async_configure( result["flow_id"], {"port": port.device, "dsmr_version": "2.2"} ) - await hass.async_block_till_done() assert result["type"] == "form" assert result["step_id"] == "setup_serial" @@ -526,7 +524,6 @@ async def test_setup_serial_wrong_telegram( result["flow_id"], {"port": port.device, "dsmr_version": "2.2"}, ) - await hass.async_block_till_done() assert result["type"] == "form" assert result["step_id"] == "setup_serial"