Skip to content

Commit f65fa38

Browse files
farmioCopilot
andauthored
Add reconfigure flow for KNX (home-assistant#145067)
Co-authored-by: Copilot <[email protected]>
1 parent 6664135 commit f65fa38

File tree

5 files changed

+290
-402
lines changed

5 files changed

+290
-402
lines changed

homeassistant/components/knx/config_flow.py

Lines changed: 82 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
from __future__ import annotations
44

5-
from abc import ABC, abstractmethod
65
from collections.abc import AsyncGenerator
76
from typing import Any, Final, Literal
87

@@ -20,8 +19,8 @@
2019
from xknx.secure.keyring import Keyring, XMLInterface
2120

2221
from homeassistant.config_entries import (
22+
SOURCE_RECONFIGURE,
2323
ConfigEntry,
24-
ConfigEntryBaseFlow,
2524
ConfigFlow,
2625
ConfigFlowResult,
2726
OptionsFlow,
@@ -103,12 +102,14 @@
103102
)
104103

105104

106-
class KNXCommonFlow(ABC, ConfigEntryBaseFlow):
107-
"""Base class for KNX flows."""
105+
class KNXConfigFlow(ConfigFlow, domain=DOMAIN):
106+
"""Handle a KNX config flow."""
107+
108+
VERSION = 1
108109

109-
def __init__(self, initial_data: KNXConfigEntryData) -> None:
110-
"""Initialize KNXCommonFlow."""
111-
self.initial_data = initial_data
110+
def __init__(self) -> None:
111+
"""Initialize KNX config flow."""
112+
self.initial_data = DEFAULT_ENTRY_DATA
112113
self.new_entry_data = KNXConfigEntryData()
113114
self.new_title: str | None = None
114115

@@ -121,19 +122,21 @@ def __init__(self, initial_data: KNXConfigEntryData) -> None:
121122
self._gatewayscanner: GatewayScanner | None = None
122123
self._async_scan_gen: AsyncGenerator[GatewayDescriptor] | None = None
123124

125+
@staticmethod
126+
@callback
127+
def async_get_options_flow(config_entry: ConfigEntry) -> KNXOptionsFlow:
128+
"""Get the options flow for this handler."""
129+
return KNXOptionsFlow(config_entry)
130+
124131
@property
125132
def _xknx(self) -> XKNX:
126133
"""Return XKNX instance."""
127-
if isinstance(self, OptionsFlow) and (
134+
if (self.source == SOURCE_RECONFIGURE) and (
128135
knx_module := self.hass.data.get(KNX_MODULE_KEY)
129136
):
130137
return knx_module.xknx
131138
return XKNX()
132139

133-
@abstractmethod
134-
def finish_flow(self) -> ConfigFlowResult:
135-
"""Finish the flow."""
136-
137140
@property
138141
def connection_type(self) -> str:
139142
"""Return the configured connection type."""
@@ -150,6 +153,61 @@ def tunnel_endpoint_ia(self) -> str | None:
150153
self.initial_data.get(CONF_KNX_TUNNEL_ENDPOINT_IA),
151154
)
152155

156+
@callback
157+
def finish_flow(self) -> ConfigFlowResult:
158+
"""Create or update the ConfigEntry."""
159+
if self.source == SOURCE_RECONFIGURE:
160+
entry = self._get_reconfigure_entry()
161+
_tunnel_endpoint_str = self.initial_data.get(
162+
CONF_KNX_TUNNEL_ENDPOINT_IA, "Tunneling"
163+
)
164+
if self.new_title and not entry.title.startswith(
165+
# Overwrite standard titles, but not user defined ones
166+
(
167+
f"KNX {self.initial_data[CONF_KNX_CONNECTION_TYPE]}",
168+
CONF_KNX_AUTOMATIC.capitalize(),
169+
"Tunneling @ ",
170+
f"{_tunnel_endpoint_str} @",
171+
"Tunneling UDP @ ",
172+
"Tunneling TCP @ ",
173+
"Secure Tunneling",
174+
"Routing as ",
175+
"Secure Routing as ",
176+
)
177+
):
178+
self.new_title = None
179+
return self.async_update_reload_and_abort(
180+
self._get_reconfigure_entry(),
181+
data_updates=self.new_entry_data,
182+
title=self.new_title or UNDEFINED,
183+
)
184+
185+
title = self.new_title or f"KNX {self.new_entry_data[CONF_KNX_CONNECTION_TYPE]}"
186+
return self.async_create_entry(
187+
title=title,
188+
data=DEFAULT_ENTRY_DATA | self.new_entry_data,
189+
)
190+
191+
async def async_step_user(
192+
self, user_input: dict[str, Any] | None = None
193+
) -> ConfigFlowResult:
194+
"""Handle a flow initialized by the user."""
195+
return await self.async_step_connection_type()
196+
197+
async def async_step_reconfigure(
198+
self, user_input: dict[str, Any] | None = None
199+
) -> ConfigFlowResult:
200+
"""Handle reconfiguration of existing entry."""
201+
entry = self._get_reconfigure_entry()
202+
self.initial_data = dict(entry.data) # type: ignore[assignment]
203+
return self.async_show_menu(
204+
step_id="reconfigure",
205+
menu_options=[
206+
"connection_type",
207+
"secure_knxkeys",
208+
],
209+
)
210+
153211
async def async_step_connection_type(
154212
self, user_input: dict | None = None
155213
) -> ConfigFlowResult:
@@ -441,7 +499,7 @@ async def async_step_manual_tunnel(
441499
)
442500
ip_address: str | None
443501
if ( # initial attempt on ConfigFlow or coming from automatic / routing
444-
(isinstance(self, ConfigFlow) or not _reconfiguring_existing_tunnel)
502+
not _reconfiguring_existing_tunnel
445503
and not user_input
446504
and self._selected_tunnel is not None
447505
): # default to first found tunnel
@@ -841,79 +899,41 @@ async def async_step_secure_key_source_menu_routing(
841899
)
842900

843901

844-
class KNXConfigFlow(KNXCommonFlow, ConfigFlow, domain=DOMAIN):
845-
"""Handle a KNX config flow."""
846-
847-
VERSION = 1
848-
849-
def __init__(self) -> None:
850-
"""Initialize KNX options flow."""
851-
super().__init__(initial_data=DEFAULT_ENTRY_DATA)
852-
853-
@staticmethod
854-
@callback
855-
def async_get_options_flow(config_entry: ConfigEntry) -> KNXOptionsFlow:
856-
"""Get the options flow for this handler."""
857-
return KNXOptionsFlow(config_entry)
858-
859-
@callback
860-
def finish_flow(self) -> ConfigFlowResult:
861-
"""Create the ConfigEntry."""
862-
title = self.new_title or f"KNX {self.new_entry_data[CONF_KNX_CONNECTION_TYPE]}"
863-
return self.async_create_entry(
864-
title=title,
865-
data=DEFAULT_ENTRY_DATA | self.new_entry_data,
866-
)
867-
868-
async def async_step_user(self, user_input: dict | None = None) -> ConfigFlowResult:
869-
"""Handle a flow initialized by the user."""
870-
return await self.async_step_connection_type()
871-
872-
873-
class KNXOptionsFlow(KNXCommonFlow, OptionsFlow):
902+
class KNXOptionsFlow(OptionsFlow):
874903
"""Handle KNX options."""
875904

876-
general_settings: dict
877-
878905
def __init__(self, config_entry: ConfigEntry) -> None:
879906
"""Initialize KNX options flow."""
880-
super().__init__(initial_data=config_entry.data) # type: ignore[arg-type]
907+
self.initial_data = dict(config_entry.data)
881908

882909
@callback
883-
def finish_flow(self) -> ConfigFlowResult:
910+
def finish_flow(self, new_entry_data: KNXConfigEntryData) -> ConfigFlowResult:
884911
"""Update the ConfigEntry and finish the flow."""
885-
new_data = DEFAULT_ENTRY_DATA | self.initial_data | self.new_entry_data
912+
new_data = self.initial_data | new_entry_data
886913
self.hass.config_entries.async_update_entry(
887914
self.config_entry,
888915
data=new_data,
889-
title=self.new_title or UNDEFINED,
890916
)
891917
return self.async_create_entry(title="", data={})
892918

893919
async def async_step_init(
894920
self, user_input: dict[str, Any] | None = None
895921
) -> ConfigFlowResult:
896922
"""Manage KNX options."""
897-
return self.async_show_menu(
898-
step_id="init",
899-
menu_options=[
900-
"connection_type",
901-
"communication_settings",
902-
"secure_knxkeys",
903-
],
904-
)
923+
return await self.async_step_communication_settings()
905924

906925
async def async_step_communication_settings(
907926
self, user_input: dict[str, Any] | None = None
908927
) -> ConfigFlowResult:
909928
"""Manage KNX communication settings."""
910929
if user_input is not None:
911-
self.new_entry_data = KNXConfigEntryData(
912-
state_updater=user_input[CONF_KNX_STATE_UPDATER],
913-
rate_limit=user_input[CONF_KNX_RATE_LIMIT],
914-
telegram_log_size=user_input[CONF_KNX_TELEGRAM_LOG_SIZE],
930+
return self.finish_flow(
931+
KNXConfigEntryData(
932+
state_updater=user_input[CONF_KNX_STATE_UPDATER],
933+
rate_limit=user_input[CONF_KNX_RATE_LIMIT],
934+
telegram_log_size=user_input[CONF_KNX_TELEGRAM_LOG_SIZE],
935+
)
915936
)
916-
return self.finish_flow()
917937

918938
data_schema = {
919939
vol.Required(

homeassistant/components/knx/quality_scale.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ rules:
104104
Since all entities are configured manually, names are user-defined.
105105
exception-translations: done
106106
icon-translations: done
107-
reconfiguration-flow: todo
107+
reconfiguration-flow: done
108108
repair-issues: todo
109109
stale-devices:
110110
status: exempt

0 commit comments

Comments
 (0)