From 266fccf0cf9c18cd2c05e0e6227faea8cbc79efa Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 5 Jun 2026 21:35:07 -0400 Subject: [PATCH] Use SerialPortSelector for DSMR serial port configuration (#171103) Co-authored-by: Claude --- homeassistant/components/dsmr/config_flow.py | 52 +------------ homeassistant/components/dsmr/strings.json | 6 -- tests/components/dsmr/test_config_flow.py | 78 -------------------- 3 files changed, 3 insertions(+), 133 deletions(-) diff --git a/homeassistant/components/dsmr/config_flow.py b/homeassistant/components/dsmr/config_flow.py index 8bc61a642bbb..80018730ca01 100644 --- a/homeassistant/components/dsmr/config_flow.py +++ b/homeassistant/components/dsmr/config_flow.py @@ -13,7 +13,6 @@ from dsmr_parser.clients.rfxtrx_protocol import ( from dsmr_parser.objects import DSMRObject import voluptuous as vol -from homeassistant.components import usb from homeassistant.config_entries import ( ConfigEntry, ConfigFlow, @@ -23,6 +22,7 @@ from homeassistant.config_entries import ( from homeassistant.const import CONF_HOST, CONF_PORT, CONF_PROTOCOL, CONF_TYPE from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers.selector import SerialPortSelector from .const import ( CONF_DSMR_VERSION, @@ -37,8 +37,6 @@ from .const import ( RFXTRX_DSMR_PROTOCOL, ) -CONF_MANUAL_PATH = "Enter Manually" - class DSMRConnection: """Test the connection to DSMR and receive telegram to read serial ids.""" @@ -165,8 +163,6 @@ class DSMRFlowHandler(ConfigFlow, domain=DOMAIN): VERSION = 1 - _dsmr_version: str | None = None - @staticmethod @callback def async_get_options_flow( @@ -222,34 +218,13 @@ class DSMRFlowHandler(ConfigFlow, domain=DOMAIN): """Step when setting up serial configuration.""" errors: dict[str, str] = {} if user_input is not None: - user_selection = user_input[CONF_PORT] - if user_selection == CONF_MANUAL_PATH: - self._dsmr_version = user_input[CONF_DSMR_VERSION] - return await self.async_step_setup_serial_manual_path() - - dev_path = user_selection - - validate_data = { - CONF_PORT: dev_path, - CONF_DSMR_VERSION: user_input[CONF_DSMR_VERSION], - } - - data = await self.async_validate_dsmr(validate_data, errors) + data = await self.async_validate_dsmr(user_input, errors) if not errors: return self.async_create_entry(title=data[CONF_PORT], data=data) - ports = await usb.async_scan_serial_ports(self.hass) - list_of_ports = { - port.device: f"{port.device} - {port.description or 'n/a'}" - f", s/n: {port.serial_number or 'n/a'}" - + (f" - {port.manufacturer}" if port.manufacturer else "") - for port in ports - } - list_of_ports[CONF_MANUAL_PATH] = CONF_MANUAL_PATH - schema = vol.Schema( { - vol.Required(CONF_PORT): vol.In(list_of_ports), + vol.Required(CONF_PORT): SerialPortSelector(), vol.Required(CONF_DSMR_VERSION): vol.In(DSMR_VERSIONS), } ) @@ -259,27 +234,6 @@ class DSMRFlowHandler(ConfigFlow, domain=DOMAIN): errors=errors, ) - async def async_step_setup_serial_manual_path( - self, user_input: dict[str, Any] | None = None - ) -> ConfigFlowResult: - """Select path manually.""" - if user_input is not None: - validate_data = { - CONF_PORT: user_input[CONF_PORT], - CONF_DSMR_VERSION: self._dsmr_version, - } - - errors: dict[str, str] = {} - data = await self.async_validate_dsmr(validate_data, errors) - if not errors: - return self.async_create_entry(title=data[CONF_PORT], data=data) - - schema = vol.Schema({vol.Required(CONF_PORT): str}) - return self.async_show_form( - step_id="setup_serial_manual_path", - data_schema=schema, - ) - async def async_validate_dsmr( self, input_data: dict[str, Any], errors: dict[str, str] ) -> dict[str, Any]: diff --git a/homeassistant/components/dsmr/strings.json b/homeassistant/components/dsmr/strings.json index 61c753b2e7f2..519c23cc5694 100644 --- a/homeassistant/components/dsmr/strings.json +++ b/homeassistant/components/dsmr/strings.json @@ -26,12 +26,6 @@ }, "title": "[%key:common::config_flow::data::device%]" }, - "setup_serial_manual_path": { - "data": { - "port": "[%key:common::config_flow::data::usb_path%]" - }, - "title": "[%key:common::config_flow::data::path%]" - }, "user": { "data": { "type": "Connection type" diff --git a/tests/components/dsmr/test_config_flow.py b/tests/components/dsmr/test_config_flow.py index a0387a0fc12d..eaadb4d4ae63 100644 --- a/tests/components/dsmr/test_config_flow.py +++ b/tests/components/dsmr/test_config_flow.py @@ -192,12 +192,7 @@ async def test_setup_network_rfxtrx( ), ], ) -@patch( - "homeassistant.components.dsmr.config_flow.usb.async_scan_serial_ports", - return_value=[com_port()], -) async def test_setup_serial( - com_mock, hass: HomeAssistant, dsmr_connection_send_validate_fixture: tuple[MagicMock, MagicMock, MagicMock], version: str, @@ -235,12 +230,7 @@ async def test_setup_serial( assert result["data"] == entry_data -@patch( - "homeassistant.components.dsmr.config_flow.usb.async_scan_serial_ports", - return_value=[com_port()], -) async def test_setup_serial_rfxtrx( - com_mock, hass: HomeAssistant, dsmr_connection_send_validate_fixture: tuple[MagicMock, MagicMock, MagicMock], rfxtrx_dsmr_connection_send_validate_fixture: tuple[ @@ -291,65 +281,7 @@ async def test_setup_serial_rfxtrx( assert result["data"] == {**entry_data, **SERIAL_DATA} -@patch( - "homeassistant.components.dsmr.config_flow.usb.async_scan_serial_ports", - return_value=[com_port()], -) -async def test_setup_serial_manual( - com_mock, - hass: HomeAssistant, - dsmr_connection_send_validate_fixture: tuple[MagicMock, MagicMock, MagicMock], -) -> None: - """Test we can setup serial with manual entry.""" - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER} - ) - - assert result["type"] is FlowResultType.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"] is FlowResultType.FORM - assert result["step_id"] == "setup_serial" - assert result["errors"] == {} - - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - {"port": "Enter Manually", "dsmr_version": "2.2"}, - ) - - assert result["type"] is FlowResultType.FORM - assert result["step_id"] == "setup_serial_manual_path" - assert result["errors"] is None - - with patch("homeassistant.components.dsmr.async_setup_entry", return_value=True): - 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", - "dsmr_version": "2.2", - "protocol": "dsmr_protocol", - } - - assert result["type"] is FlowResultType.CREATE_ENTRY - assert result["title"] == "/dev/ttyUSB0" - assert result["data"] == {**entry_data, **SERIAL_DATA} - - -@patch( - "homeassistant.components.dsmr.config_flow.usb.async_scan_serial_ports", - return_value=[com_port()], -) async def test_setup_serial_fail( - com_mock, hass: HomeAssistant, dsmr_connection_send_validate_fixture: tuple[MagicMock, MagicMock, MagicMock], ) -> None: @@ -395,12 +327,7 @@ async def test_setup_serial_fail( assert result["errors"] == {"base": "cannot_connect"} -@patch( - "homeassistant.components.dsmr.config_flow.usb.async_scan_serial_ports", - return_value=[com_port()], -) async def test_setup_serial_timeout( - com_mock, hass: HomeAssistant, dsmr_connection_send_validate_fixture: tuple[MagicMock, MagicMock, MagicMock], rfxtrx_dsmr_connection_send_validate_fixture: tuple[ @@ -456,12 +383,7 @@ async def test_setup_serial_timeout( assert result["errors"] == {"base": "cannot_communicate"} -@patch( - "homeassistant.components.dsmr.config_flow.usb.async_scan_serial_ports", - return_value=[com_port()], -) async def test_setup_serial_wrong_telegram( - com_mock, hass: HomeAssistant, dsmr_connection_send_validate_fixture: tuple[MagicMock, MagicMock, MagicMock], rfxtrx_dsmr_connection_send_validate_fixture: tuple[