From b9247f395273b2b80ae5760996c58b8b2e922469 Mon Sep 17 00:00:00 2001
From: Marvin Wichmann <marvin@fam-wichmann.de>
Date: Thu, 30 Dec 2021 01:13:58 +0100
Subject: [PATCH] Fix local_ip handling in KNX options flow (#62969)

---
 homeassistant/components/knx/config_flow.py   | 21 +++--
 homeassistant/components/knx/strings.json     |  6 +-
 .../components/knx/translations/en.json       |  7 +-
 tests/components/knx/test_config_flow.py      | 77 ++++++++++++++-----
 4 files changed, 79 insertions(+), 32 deletions(-)

diff --git a/homeassistant/components/knx/config_flow.py b/homeassistant/components/knx/config_flow.py
index 96aa8f67e3b..01e71eb37af 100644
--- a/homeassistant/components/knx/config_flow.py
+++ b/homeassistant/components/knx/config_flow.py
@@ -28,6 +28,7 @@ from .schema import ConnectionSchema
 
 CONF_KNX_GATEWAY: Final = "gateway"
 CONF_MAX_RATE_LIMIT: Final = 60
+CONF_DEFAULT_LOCAL_IP: Final = "0.0.0.0"
 
 DEFAULT_ENTRY_DATA: Final = {
     ConnectionSchema.CONF_KNX_STATE_UPDATER: ConnectionSchema.CONF_KNX_DEFAULT_STATE_UPDATER,
@@ -328,6 +329,12 @@ class KNXOptionsFlowHandler(OptionsFlow):
         entry_data = {
             **DEFAULT_ENTRY_DATA,
             **self.general_settings,
+            ConnectionSchema.CONF_KNX_LOCAL_IP: self.general_settings.get(
+                ConnectionSchema.CONF_KNX_LOCAL_IP
+            )
+            if self.general_settings.get(ConnectionSchema.CONF_KNX_LOCAL_IP)
+            != CONF_DEFAULT_LOCAL_IP
+            else None,
             CONF_HOST: self.current_config.get(CONF_HOST, ""),
         }
 
@@ -337,7 +344,7 @@ class KNXOptionsFlowHandler(OptionsFlow):
                 **user_input,
             }
 
-        entry_title = entry_data[CONF_KNX_CONNECTION_TYPE].capitalize()
+        entry_title = str(entry_data[CONF_KNX_CONNECTION_TYPE]).capitalize()
         if entry_data[CONF_KNX_CONNECTION_TYPE] == CONF_KNX_TUNNELING:
             entry_title = f"{CONF_KNX_TUNNELING.capitalize()} @ {entry_data[CONF_HOST]}"
 
@@ -388,12 +395,16 @@ class KNXOptionsFlowHandler(OptionsFlow):
         }
 
         if self.show_advanced_options:
+            local_ip = (
+                self.current_config.get(ConnectionSchema.CONF_KNX_LOCAL_IP)
+                if self.current_config.get(ConnectionSchema.CONF_KNX_LOCAL_IP)
+                is not None
+                else CONF_DEFAULT_LOCAL_IP
+            )
             data_schema[
-                vol.Optional(
+                vol.Required(
                     ConnectionSchema.CONF_KNX_LOCAL_IP,
-                    default=self.current_config.get(
-                        ConnectionSchema.CONF_KNX_LOCAL_IP,
-                    ),
+                    default=local_ip,
                 )
             ] = str
             data_schema[
diff --git a/homeassistant/components/knx/strings.json b/homeassistant/components/knx/strings.json
index 4db92888aab..d219880be5c 100644
--- a/homeassistant/components/knx/strings.json
+++ b/homeassistant/components/knx/strings.json
@@ -20,7 +20,7 @@
           "host": "[%key:common::config_flow::data::host%]",
           "individual_address": "Individual address for the connection",
           "route_back": "Route Back / NAT Mode",
-          "local_ip": "Local IP (leave empty if unsure)"
+          "local_ip": "Local IP of Home Assistant (leave empty for automatic detection)"
         }
       },
       "routing": {
@@ -29,7 +29,7 @@
           "individual_address": "Individual address for the routing connection",
           "multicast_group": "The multicast group used for routing",
           "multicast_port": "The multicast port used for routing",
-          "local_ip": "Local IP (leave empty if unsure)"
+          "local_ip": "Local IP of Home Assistant (leave empty for automatic detection)"
         }
       }
     },
@@ -49,7 +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)",
+          "local_ip": "Local IP of Home Assistant (use 0.0.0.0 for automatic detection)",
           "state_updater": "Globally enable reading states from the KNX Bus",
           "rate_limit": "Maximum outgoing telegrams per second"
         }
diff --git a/homeassistant/components/knx/translations/en.json b/homeassistant/components/knx/translations/en.json
index 8f812a7baf1..354675c5007 100644
--- a/homeassistant/components/knx/translations/en.json
+++ b/homeassistant/components/knx/translations/en.json
@@ -12,7 +12,7 @@
                 "data": {
                     "host": "Host",
                     "individual_address": "Individual address for the connection",
-                    "local_ip": "Local IP (leave empty if unsure)",
+                    "local_ip": "Local IP of Home Assistant (leave empty for automatic detection)",
                     "port": "Port",
                     "route_back": "Route Back / NAT Mode"
                 },
@@ -21,7 +21,7 @@
             "routing": {
                 "data": {
                     "individual_address": "Individual address for the routing connection",
-                    "local_ip": "Local IP (leave empty if unsure)",
+                    "local_ip": "Local IP of Home Assistant (leave empty for automatic detection)",
                     "multicast_group": "The multicast group used for routing",
                     "multicast_port": "The multicast port used for routing"
                 },
@@ -47,7 +47,7 @@
                 "data": {
                     "connection_type": "KNX Connection Type",
                     "individual_address": "Default individual address",
-                    "local_ip": "Local IP (leave empty if unsure)",
+                    "local_ip": "Local IP of Home Assistant (use 0.0.0.0 for automatic detection)",
                     "multicast_group": "Multicast group used for routing and discovery",
                     "multicast_port": "Multicast port used for routing and discovery",
                     "rate_limit": "Maximum outgoing telegrams per second",
@@ -57,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 4f3e1734b69..aec757a1086 100644
--- a/tests/components/knx/test_config_flow.py
+++ b/tests/components/knx/test_config_flow.py
@@ -1,6 +1,7 @@
 """Test the KNX config flow."""
 from unittest.mock import patch
 
+import pytest
 from xknx import XKNX
 from xknx.io import DEFAULT_MCAST_GRP
 from xknx.io.gateway_scanner import GatewayDescriptor
@@ -8,6 +9,7 @@ from xknx.io.gateway_scanner import GatewayDescriptor
 from homeassistant import config_entries
 from homeassistant.components.knx import ConnectionSchema
 from homeassistant.components.knx.config_flow import (
+    CONF_DEFAULT_LOCAL_IP,
     CONF_KNX_GATEWAY,
     DEFAULT_ENTRY_DATA,
 )
@@ -585,6 +587,7 @@ async def test_options_flow(
             CONF_KNX_CONNECTION_TYPE: CONF_KNX_AUTOMATIC,
             CONF_KNX_INDIVIDUAL_ADDRESS: "15.15.255",
             CONF_HOST: "",
+            ConnectionSchema.CONF_KNX_LOCAL_IP: None,
             ConnectionSchema.CONF_KNX_MCAST_PORT: 3675,
             ConnectionSchema.CONF_KNX_MCAST_GRP: DEFAULT_MCAST_GRP,
             ConnectionSchema.CONF_KNX_RATE_LIMIT: 20,
@@ -643,14 +646,65 @@ async def test_tunneling_options_flow(
             ConnectionSchema.CONF_KNX_MCAST_GRP: DEFAULT_MCAST_GRP,
             ConnectionSchema.CONF_KNX_RATE_LIMIT: 20,
             ConnectionSchema.CONF_KNX_STATE_UPDATER: True,
+            ConnectionSchema.CONF_KNX_LOCAL_IP: None,
             CONF_HOST: "192.168.1.1",
             CONF_PORT: 3675,
             ConnectionSchema.CONF_KNX_ROUTE_BACK: True,
         }
 
 
+@pytest.mark.parametrize(
+    "user_input,config_entry_data",
+    [
+        (
+            {
+                CONF_KNX_CONNECTION_TYPE: CONF_KNX_AUTOMATIC,
+                CONF_KNX_INDIVIDUAL_ADDRESS: "15.15.250",
+                ConnectionSchema.CONF_KNX_MCAST_PORT: 3675,
+                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",
+            },
+            {
+                CONF_KNX_CONNECTION_TYPE: CONF_KNX_AUTOMATIC,
+                CONF_KNX_INDIVIDUAL_ADDRESS: "15.15.250",
+                CONF_HOST: "",
+                ConnectionSchema.CONF_KNX_MCAST_PORT: 3675,
+                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",
+            },
+        ),
+        (
+            {
+                CONF_KNX_CONNECTION_TYPE: CONF_KNX_AUTOMATIC,
+                CONF_KNX_INDIVIDUAL_ADDRESS: "15.15.250",
+                ConnectionSchema.CONF_KNX_MCAST_PORT: 3675,
+                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: CONF_DEFAULT_LOCAL_IP,
+            },
+            {
+                CONF_KNX_CONNECTION_TYPE: CONF_KNX_AUTOMATIC,
+                CONF_KNX_INDIVIDUAL_ADDRESS: "15.15.250",
+                CONF_HOST: "",
+                ConnectionSchema.CONF_KNX_MCAST_PORT: 3675,
+                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: None,
+            },
+        ),
+    ],
+)
 async def test_advanced_options(
-    hass: HomeAssistant, mock_config_entry: MockConfigEntry
+    hass: HomeAssistant,
+    mock_config_entry: MockConfigEntry,
+    user_input,
+    config_entry_data,
 ) -> None:
     """Test options config flow."""
     mock_config_entry.add_to_hass(hass)
@@ -668,28 +722,11 @@ async def test_advanced_options(
 
         result2 = await hass.config_entries.options.async_configure(
             result["flow_id"],
-            user_input={
-                CONF_KNX_CONNECTION_TYPE: CONF_KNX_AUTOMATIC,
-                CONF_KNX_INDIVIDUAL_ADDRESS: "15.15.250",
-                ConnectionSchema.CONF_KNX_MCAST_PORT: 3675,
-                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",
-            },
+            user_input=user_input,
         )
 
         await hass.async_block_till_done()
         assert result2.get("type") == RESULT_TYPE_CREATE_ENTRY
         assert not result2.get("data")
 
-        assert mock_config_entry.data == {
-            CONF_KNX_CONNECTION_TYPE: CONF_KNX_AUTOMATIC,
-            CONF_KNX_INDIVIDUAL_ADDRESS: "15.15.250",
-            CONF_HOST: "",
-            ConnectionSchema.CONF_KNX_MCAST_PORT: 3675,
-            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",
-        }
+        assert mock_config_entry.data == config_entry_data