diff --git a/homeassistant/components/knx/__init__.py b/homeassistant/components/knx/__init__.py index 3e75c614f1e..ba6689a023d 100644 --- a/homeassistant/components/knx/__init__.py +++ b/homeassistant/components/knx/__init__.py @@ -390,6 +390,7 @@ class KNXModule: connection_type=ConnectionType.TUNNELING, gateway_ip=self.config[CONF_HOST], gateway_port=self.config[CONF_PORT], + local_ip=self.config.get(ConnectionSchema.CONF_KNX_LOCAL_IP), route_back=self.config.get(ConnectionSchema.CONF_KNX_ROUTE_BACK, False), auto_reconnect=True, ) diff --git a/homeassistant/components/knx/config_flow.py b/homeassistant/components/knx/config_flow.py index 3fcf4069624..30071752731 100644 --- a/homeassistant/components/knx/config_flow.py +++ b/homeassistant/components/knx/config_flow.py @@ -121,6 +121,9 @@ class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): ConnectionSchema.CONF_KNX_ROUTE_BACK: user_input[ ConnectionSchema.CONF_KNX_ROUTE_BACK ], + ConnectionSchema.CONF_KNX_LOCAL_IP: user_input.get( + ConnectionSchema.CONF_KNX_LOCAL_IP + ), CONF_KNX_CONNECTION_TYPE: CONF_KNX_TUNNELING, }, ) @@ -134,6 +137,7 @@ 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, } return self.async_show_form( @@ -243,6 +247,9 @@ class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): **DEFAULT_ENTRY_DATA, CONF_HOST: config[CONF_KNX_TUNNELING][CONF_HOST], CONF_PORT: config[CONF_KNX_TUNNELING][CONF_PORT], + ConnectionSchema.CONF_KNX_LOCAL_IP: config[CONF_KNX_TUNNELING].get( + ConnectionSchema.CONF_KNX_LOCAL_IP + ), ConnectionSchema.CONF_KNX_ROUTE_BACK: config[CONF_KNX_TUNNELING][ ConnectionSchema.CONF_KNX_ROUTE_BACK ], @@ -299,6 +306,7 @@ 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( diff --git a/homeassistant/components/knx/strings.json b/homeassistant/components/knx/strings.json index ff191f7a4ce..7f770c25427 100644 --- a/homeassistant/components/knx/strings.json +++ b/homeassistant/components/knx/strings.json @@ -19,7 +19,8 @@ "port": "[%key:common::config_flow::data::port%]", "host": "[%key:common::config_flow::data::host%]", "individual_address": "Individual address for the connection", - "route_back": "Route Back / NAT Mode" + "route_back": "Route Back / NAT Mode", + "local_ip": "Local IP (leave empty if unsure)" } }, "routing": { @@ -55,7 +56,8 @@ "data": { "port": "[%key:common::config_flow::data::port%]", "host": "[%key:common::config_flow::data::host%]", - "route_back": "Route Back / NAT Mode" + "route_back": "Route Back / NAT Mode", + "local_ip": "Local IP (leave empty if unsure)" } } } diff --git a/homeassistant/components/knx/translations/en.json b/homeassistant/components/knx/translations/en.json index 80890538fbc..5320f0cfb03 100644 --- a/homeassistant/components/knx/translations/en.json +++ b/homeassistant/components/knx/translations/en.json @@ -12,6 +12,7 @@ "data": { "host": "Host", "individual_address": "Individual address for the connection", + "local_ip": "Local IP (leave empty if unsure)", "port": "Port", "route_back": "Route Back / NAT Mode" }, @@ -54,6 +55,7 @@ "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 2b792044fe5..ff1fc362aa5 100644 --- a/tests/components/knx/test_config_flow.py +++ b/tests/components/knx/test_config_flow.py @@ -132,6 +132,57 @@ async def test_tunneling_setup(hass: HomeAssistant) -> None: CONF_PORT: 3675, CONF_KNX_INDIVIDUAL_ADDRESS: "15.15.250", ConnectionSchema.CONF_KNX_ROUTE_BACK: False, + ConnectionSchema.CONF_KNX_LOCAL_IP: None, + } + + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_tunneling_setup_for_local_ip(hass: HomeAssistant) -> None: + """Test tunneling if only one gateway is found.""" + gateway = _gateway_descriptor("192.168.0.2", 3675) + 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} + ) + 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_TUNNELING, + }, + ) + await hass.async_block_till_done() + assert result2["type"] == RESULT_TYPE_FORM + assert result2["step_id"] == "manual_tunnel" + 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"], + { + CONF_HOST: "192.168.0.2", + CONF_PORT: 3675, + 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"] == "Tunneling @ 192.168.0.2" + assert result3["data"] == { + **DEFAULT_ENTRY_DATA, + CONF_KNX_CONNECTION_TYPE: CONF_KNX_TUNNELING, + CONF_HOST: "192.168.0.2", + CONF_PORT: 3675, + CONF_KNX_INDIVIDUAL_ADDRESS: "15.15.250", + ConnectionSchema.CONF_KNX_ROUTE_BACK: False, + ConnectionSchema.CONF_KNX_LOCAL_IP: "192.168.1.112", } assert len(mock_setup_entry.mock_calls) == 1 @@ -188,6 +239,7 @@ async def test_tunneling_setup_for_multiple_found_gateways(hass: HomeAssistant) CONF_PORT: 3675, CONF_KNX_INDIVIDUAL_ADDRESS: "15.15.250", ConnectionSchema.CONF_KNX_ROUTE_BACK: False, + ConnectionSchema.CONF_KNX_LOCAL_IP: None, } assert len(mock_setup_entry.mock_calls) == 1 @@ -261,6 +313,7 @@ async def test_import_config_tunneling(hass: HomeAssistant) -> None: CONF_KNX_TUNNELING: { CONF_HOST: "192.168.1.1", CONF_PORT: 3675, + ConnectionSchema.CONF_KNX_LOCAL_IP: "192.168.1.112", ConnectionSchema.CONF_KNX_ROUTE_BACK: True, }, } @@ -284,6 +337,7 @@ async def test_import_config_tunneling(hass: HomeAssistant) -> None: ConnectionSchema.CONF_KNX_STATE_UPDATER: True, ConnectionSchema.CONF_KNX_RATE_LIMIT: 20, ConnectionSchema.CONF_KNX_MCAST_PORT: 3675, + ConnectionSchema.CONF_KNX_LOCAL_IP: "192.168.1.112", ConnectionSchema.CONF_KNX_MCAST_GRP: DEFAULT_MCAST_GRP, } @@ -509,6 +563,7 @@ 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", }, ) @@ -526,6 +581,7 @@ 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", }