From cefbc2c4281c7153fbe35d5fc8ff7b4b8c23fed3 Mon Sep 17 00:00:00 2001 From: Marvin Wichmann Date: Wed, 15 Dec 2021 13:15:56 +0100 Subject: [PATCH] Allow setting local_ip for knx routing connections (#61836) --- homeassistant/components/knx/__init__.py | 1 + homeassistant/components/knx/config_flow.py | 19 ++++- homeassistant/components/knx/strings.json | 7 +- .../components/knx/translations/en.json | 5 +- tests/components/knx/test_config_flow.py | 64 ++++++++++++++- tests/components/knx/test_init.py | 78 +++++++++++++++++++ 6 files changed, 164 insertions(+), 10 deletions(-) create mode 100644 tests/components/knx/test_init.py diff --git a/homeassistant/components/knx/__init__.py b/homeassistant/components/knx/__init__.py index ba6689a023d..5a66824fbcb 100644 --- a/homeassistant/components/knx/__init__.py +++ b/homeassistant/components/knx/__init__.py @@ -383,6 +383,7 @@ class KNXModule: if _conn_type == CONF_KNX_ROUTING: return ConnectionConfig( connection_type=ConnectionType.ROUTING, + local_ip=self.config.get(ConnectionSchema.CONF_KNX_LOCAL_IP), auto_reconnect=True, ) if _conn_type == CONF_KNX_TUNNELING: diff --git a/homeassistant/components/knx/config_flow.py b/homeassistant/components/knx/config_flow.py index 30071752731..96aa8f67e3b 100644 --- a/homeassistant/components/knx/config_flow.py +++ b/homeassistant/components/knx/config_flow.py @@ -137,9 +137,11 @@ class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): vol.Required( ConnectionSchema.CONF_KNX_ROUTE_BACK, default=False ): vol.Coerce(bool), - vol.Optional(ConnectionSchema.CONF_KNX_LOCAL_IP): str, } + if self.show_advanced_options: + fields[vol.Optional(ConnectionSchema.CONF_KNX_LOCAL_IP)] = str + return self.async_show_form( step_id="manual_tunnel", data_schema=vol.Schema(fields), errors=errors ) @@ -195,6 +197,9 @@ class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): CONF_KNX_INDIVIDUAL_ADDRESS: user_input[ CONF_KNX_INDIVIDUAL_ADDRESS ], + ConnectionSchema.CONF_KNX_LOCAL_IP: user_input.get( + ConnectionSchema.CONF_KNX_LOCAL_IP + ), CONF_KNX_CONNECTION_TYPE: CONF_KNX_ROUTING, }, ) @@ -211,6 +216,9 @@ class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): ): cv.port, } + if self.show_advanced_options: + fields[vol.Optional(ConnectionSchema.CONF_KNX_LOCAL_IP)] = str + return self.async_show_form( step_id="routing", data_schema=vol.Schema(fields), errors=errors ) @@ -306,7 +314,6 @@ class KNXOptionsFlowHandler(OptionsFlow): vol.Required( CONF_PORT, default=self.current_config.get(CONF_PORT, 3671) ): cv.port, - vol.Optional(ConnectionSchema.CONF_KNX_LOCAL_IP): str, vol.Required( ConnectionSchema.CONF_KNX_ROUTE_BACK, default=self.current_config.get( @@ -381,6 +388,14 @@ class KNXOptionsFlowHandler(OptionsFlow): } if self.show_advanced_options: + data_schema[ + vol.Optional( + ConnectionSchema.CONF_KNX_LOCAL_IP, + default=self.current_config.get( + ConnectionSchema.CONF_KNX_LOCAL_IP, + ), + ) + ] = str data_schema[ vol.Required( ConnectionSchema.CONF_KNX_STATE_UPDATER, diff --git a/homeassistant/components/knx/strings.json b/homeassistant/components/knx/strings.json index 7f770c25427..4db92888aab 100644 --- a/homeassistant/components/knx/strings.json +++ b/homeassistant/components/knx/strings.json @@ -28,7 +28,8 @@ "data": { "individual_address": "Individual address for the routing connection", "multicast_group": "The multicast group used for routing", - "multicast_port": "The multicast port used for routing" + "multicast_port": "The multicast port used for routing", + "local_ip": "Local IP (leave empty if unsure)" } } }, @@ -48,6 +49,7 @@ "individual_address": "Default individual address", "multicast_group": "Multicast group used for routing and discovery", "multicast_port": "Multicast port used for routing and discovery", + "local_ip": "Local IP (leave empty if unsure)", "state_updater": "Globally enable reading states from the KNX Bus", "rate_limit": "Maximum outgoing telegrams per second" } @@ -56,8 +58,7 @@ "data": { "port": "[%key:common::config_flow::data::port%]", "host": "[%key:common::config_flow::data::host%]", - "route_back": "Route Back / NAT Mode", - "local_ip": "Local IP (leave empty if unsure)" + "route_back": "Route Back / NAT Mode" } } } diff --git a/homeassistant/components/knx/translations/en.json b/homeassistant/components/knx/translations/en.json index 5320f0cfb03..91b9dfce5f3 100644 --- a/homeassistant/components/knx/translations/en.json +++ b/homeassistant/components/knx/translations/en.json @@ -22,7 +22,8 @@ "data": { "individual_address": "Individual address for the routing connection", "multicast_group": "The multicast group used for routing", - "multicast_port": "The multicast port used for routing" + "multicast_port": "The multicast port used for routing", + "local_ip": "Local IP (leave empty if unsure)" }, "description": "Please configure the routing options." }, @@ -48,6 +49,7 @@ "individual_address": "Default individual address", "multicast_group": "Multicast group used for routing and discovery", "multicast_port": "Multicast port used for routing and discovery", + "local_ip": "Local IP (leave empty if unsure)", "rate_limit": "Maximum outgoing telegrams per second", "state_updater": "Globally enable reading states from the KNX Bus" } @@ -55,7 +57,6 @@ "tunnel": { "data": { "host": "Host", - "local_ip": "Local IP (leave empty if unsure)", "port": "Port", "route_back": "Route Back / NAT Mode" } diff --git a/tests/components/knx/test_config_flow.py b/tests/components/knx/test_config_flow.py index ff1fc362aa5..65289c2b173 100644 --- a/tests/components/knx/test_config_flow.py +++ b/tests/components/knx/test_config_flow.py @@ -83,6 +83,60 @@ async def test_routing_setup(hass: HomeAssistant) -> None: CONF_KNX_CONNECTION_TYPE: CONF_KNX_ROUTING, ConnectionSchema.CONF_KNX_MCAST_GRP: DEFAULT_MCAST_GRP, ConnectionSchema.CONF_KNX_MCAST_PORT: 3675, + ConnectionSchema.CONF_KNX_LOCAL_IP: None, + CONF_KNX_INDIVIDUAL_ADDRESS: "1.1.110", + } + + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_routing_setup_advanced(hass: HomeAssistant) -> None: + """Test routing setup with advanced options.""" + with patch("xknx.io.gateway_scanner.GatewayScanner.scan") as gateways: + gateways.return_value = [] + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={ + "source": config_entries.SOURCE_USER, + "show_advanced_options": True, + }, + ) + assert result["type"] == RESULT_TYPE_FORM + assert not result["errors"] + + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_KNX_CONNECTION_TYPE: CONF_KNX_ROUTING, + }, + ) + await hass.async_block_till_done() + assert result2["type"] == RESULT_TYPE_FORM + assert result2["step_id"] == "routing" + assert not result2["errors"] + + with patch( + "homeassistant.components.knx.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result3 = await hass.config_entries.flow.async_configure( + result2["flow_id"], + { + ConnectionSchema.CONF_KNX_MCAST_GRP: DEFAULT_MCAST_GRP, + ConnectionSchema.CONF_KNX_MCAST_PORT: 3675, + CONF_KNX_INDIVIDUAL_ADDRESS: "1.1.110", + ConnectionSchema.CONF_KNX_LOCAL_IP: "192.168.1.112", + }, + ) + await hass.async_block_till_done() + assert result3["type"] == RESULT_TYPE_CREATE_ENTRY + assert result3["title"] == CONF_KNX_ROUTING.capitalize() + assert result3["data"] == { + **DEFAULT_ENTRY_DATA, + CONF_KNX_CONNECTION_TYPE: CONF_KNX_ROUTING, + ConnectionSchema.CONF_KNX_MCAST_GRP: DEFAULT_MCAST_GRP, + ConnectionSchema.CONF_KNX_MCAST_PORT: 3675, + ConnectionSchema.CONF_KNX_LOCAL_IP: "192.168.1.112", CONF_KNX_INDIVIDUAL_ADDRESS: "1.1.110", } @@ -144,7 +198,11 @@ async def test_tunneling_setup_for_local_ip(hass: HomeAssistant) -> None: with patch("xknx.io.gateway_scanner.GatewayScanner.scan") as gateways: gateways.return_value = [gateway] result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER} + DOMAIN, + context={ + "source": config_entries.SOURCE_USER, + "show_advanced_options": True, + }, ) assert result["type"] == RESULT_TYPE_FORM assert not result["errors"] @@ -563,7 +621,6 @@ async def test_tunneling_options_flow( CONF_HOST: "192.168.1.1", CONF_PORT: 3675, ConnectionSchema.CONF_KNX_ROUTE_BACK: True, - ConnectionSchema.CONF_KNX_LOCAL_IP: "192.168.1.112", }, ) @@ -581,7 +638,6 @@ async def test_tunneling_options_flow( CONF_HOST: "192.168.1.1", CONF_PORT: 3675, ConnectionSchema.CONF_KNX_ROUTE_BACK: True, - ConnectionSchema.CONF_KNX_LOCAL_IP: "192.168.1.112", } @@ -611,6 +667,7 @@ async def test_advanced_options( ConnectionSchema.CONF_KNX_MCAST_GRP: DEFAULT_MCAST_GRP, ConnectionSchema.CONF_KNX_RATE_LIMIT: 25, ConnectionSchema.CONF_KNX_STATE_UPDATER: False, + ConnectionSchema.CONF_KNX_LOCAL_IP: "192.168.1.112", }, ) @@ -626,4 +683,5 @@ async def test_advanced_options( ConnectionSchema.CONF_KNX_MCAST_GRP: DEFAULT_MCAST_GRP, ConnectionSchema.CONF_KNX_RATE_LIMIT: 25, ConnectionSchema.CONF_KNX_STATE_UPDATER: False, + ConnectionSchema.CONF_KNX_LOCAL_IP: "192.168.1.112", } diff --git a/tests/components/knx/test_init.py b/tests/components/knx/test_init.py new file mode 100644 index 00000000000..4380b132cbd --- /dev/null +++ b/tests/components/knx/test_init.py @@ -0,0 +1,78 @@ +"""Test KNX init.""" +import pytest +from xknx import XKNX +from xknx.io import ConnectionConfig, ConnectionType + +from homeassistant.components.knx.const import ( + CONF_KNX_AUTOMATIC, + CONF_KNX_CONNECTION_TYPE, + CONF_KNX_INDIVIDUAL_ADDRESS, + CONF_KNX_ROUTING, + CONF_KNX_TUNNELING, + DOMAIN as KNX_DOMAIN, +) +from homeassistant.components.knx.schema import ConnectionSchema +from homeassistant.const import CONF_HOST, CONF_PORT +from homeassistant.core import HomeAssistant + +from .conftest import KNXTestKit + +from tests.common import MockConfigEntry + + +@pytest.mark.parametrize( + "config_entry_data,connection_config", + [ + ( + { + CONF_KNX_INDIVIDUAL_ADDRESS: XKNX.DEFAULT_ADDRESS, + CONF_KNX_CONNECTION_TYPE: CONF_KNX_AUTOMATIC, + }, + ConnectionConfig(), + ), + ( + { + CONF_KNX_CONNECTION_TYPE: CONF_KNX_ROUTING, + ConnectionSchema.CONF_KNX_LOCAL_IP: "192.168.1.1", + }, + ConnectionConfig( + connection_type=ConnectionType.ROUTING, local_ip="192.168.1.1" + ), + ), + ( + { + CONF_KNX_CONNECTION_TYPE: CONF_KNX_TUNNELING, + CONF_HOST: "192.168.0.2", + CONF_PORT: 3675, + ConnectionSchema.CONF_KNX_ROUTE_BACK: False, + ConnectionSchema.CONF_KNX_LOCAL_IP: "192.168.1.112", + }, + ConnectionConfig( + connection_type=ConnectionType.TUNNELING, + route_back=False, + gateway_ip="192.168.0.2", + gateway_port=3675, + local_ip="192.168.1.112", + auto_reconnect=True, + ), + ), + ], +) +async def test_init_connection_handling( + hass: HomeAssistant, knx: KNXTestKit, config_entry_data, connection_config +): + """Test correctly generating connection config.""" + + config_entry = MockConfigEntry( + title="KNX", + domain=KNX_DOMAIN, + data=config_entry_data, + ) + knx.mock_config_entry = config_entry + await knx.setup_integration({}) + + assert hass.data.get(KNX_DOMAIN) is not None + + assert ( + hass.data[KNX_DOMAIN].connection_config().__dict__ == connection_config.__dict__ + )