33from __future__ import annotations
44
55from copy import deepcopy
6+ import logging
67from typing import Any , Self
78
89from plugwise import Smile
6970
7071type PlugwiseConfigEntry = ConfigEntry [PlugwiseDataUpdateCoordinator ]
7172
73+ _LOGGER = logging .getLogger (__name__ )
74+
7275# Upstream basically the whole file (excluding the pw-beta options)
7376
77+ SMILE_RECONF_SCHEMA = vol .Schema (
78+ {
79+ vol .Required (CONF_HOST ): str ,
80+ }
81+ )
82+
7483
75- def base_schema (
76- cf_input : ZeroconfServiceInfo | dict [str , Any ] | None ,
77- ) -> vol .Schema :
84+ def smile_user_schema (cf_input : ZeroconfServiceInfo | dict [str , Any ] | None ) -> vol .Schema :
7885 """Generate base schema for gateways."""
7986 if not cf_input : # no discovery- or user-input available
8087 return vol .Schema (
8188 {
8289 vol .Required (CONF_HOST ): str ,
8390 vol .Required (CONF_PASSWORD ): str ,
91+ # Port under investigation for removal (hence not added in #132878)
8492 vol .Optional (CONF_PORT , default = DEFAULT_PORT ): int ,
8593 vol .Required (CONF_USERNAME , default = SMILE ): vol .In (
8694 {SMILE : FLOW_SMILE , STRETCH : FLOW_STRETCH }
@@ -106,7 +114,7 @@ def base_schema(
106114async def validate_input (hass : HomeAssistant , data : dict [str , Any ]) -> Smile :
107115 """Validate whether the user input allows us to connect to the gateway.
108116
109- Data has the keys from base_schema() with values provided by the user.
117+ Data has the keys from the schema with values provided by the user.
110118 """
111119 websession = async_get_clientsession (hass , verify_ssl = False )
112120 api = Smile (
@@ -120,6 +128,32 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> Smile:
120128 return api
121129
122130
131+ async def verify_connection (
132+ hass : HomeAssistant , user_input : dict [str , Any ]
133+ ) -> tuple [Smile | None , dict [str , str ]]:
134+ """Verify and return the gateway connection or an error."""
135+ errors : dict [str , str ] = {}
136+
137+ try :
138+ return (await validate_input (hass , user_input ), errors )
139+ except ConnectionFailedError :
140+ errors [CONF_BASE ] = "cannot_connect"
141+ except InvalidAuthentication :
142+ errors [CONF_BASE ] = "invalid_auth"
143+ except InvalidSetupError :
144+ errors [CONF_BASE ] = "invalid_setup"
145+ except (InvalidXMLError , ResponseError ):
146+ errors [CONF_BASE ] = "response_error"
147+ except UnsupportedDeviceError :
148+ errors [CONF_BASE ] = "unsupported"
149+ except Exception : # noqa: BLE001
150+ _LOGGER .exception (
151+ "Unknown exception while verifying connection with your Plugwise Smile"
152+ )
153+ errors [CONF_BASE ] = "unknown"
154+ return (None , errors )
155+
156+
123157class PlugwiseConfigFlow (ConfigFlow , domain = DOMAIN ):
124158 """Handle a config flow for Plugwise Smile."""
125159
@@ -197,51 +231,71 @@ def is_matching(self, other_flow: Self) -> bool:
197231
198232 return False
199233
234+
200235 async def async_step_user (
201236 self , user_input : dict [str , Any ] | None = None
202237 ) -> ConfigFlowResult :
203238 """Handle the initial step when using network/gateway setups."""
204239 errors : dict [str , str ] = {}
205240
206- if not user_input :
207- return self .async_show_form (
208- step_id = SOURCE_USER ,
209- data_schema = base_schema (self .discovery_info ),
210- errors = errors ,
211- )
212-
213- if self .discovery_info :
214- user_input [CONF_HOST ] = self .discovery_info .host
215- user_input [CONF_PORT ] = self .discovery_info .port
216- user_input [CONF_USERNAME ] = self ._username
217-
218- try :
219- api = await validate_input (self .hass , user_input )
220- except ConnectionFailedError :
221- errors [CONF_BASE ] = "cannot_connect"
222- except InvalidAuthentication :
223- errors [CONF_BASE ] = "invalid_auth"
224- except InvalidSetupError :
225- errors [CONF_BASE ] = "invalid_setup"
226- except (InvalidXMLError , ResponseError ):
227- errors [CONF_BASE ] = "response_error"
228- except UnsupportedDeviceError :
229- errors [CONF_BASE ] = "unsupported"
230- except Exception : # noqa: BLE001
231- errors [CONF_BASE ] = "unknown"
232-
233- if errors :
234- return self .async_show_form (
235- step_id = SOURCE_USER ,
236- data_schema = base_schema (user_input ),
237- errors = errors ,
238- )
239-
240- await self .async_set_unique_id (
241- api .smile_hostname or api .gateway_id , raise_on_progress = False
241+ if user_input is not None :
242+ if self .discovery_info :
243+ user_input [CONF_HOST ] = self .discovery_info .host
244+ user_input [CONF_PORT ] = self .discovery_info .port
245+ user_input [CONF_USERNAME ] = self ._username
246+
247+ api , errors = await verify_connection (self .hass , user_input )
248+ if api :
249+ await self .async_set_unique_id (
250+ api .smile_hostname or api .gateway_id , raise_on_progress = False
251+ )
252+ self ._abort_if_unique_id_configured ()
253+ return self .async_create_entry (title = api .smile_name , data = user_input )
254+
255+ return self .async_show_form (
256+ step_id = SOURCE_USER ,
257+ data_schema = smile_user_schema (self .discovery_info ),
258+ errors = errors ,
242259 )
243- self ._abort_if_unique_id_configured ()
244- return self .async_create_entry (title = api .smile_name , data = user_input )
260+
261+ async def async_step_reconfigure (
262+ self , user_input : dict [str , Any ] | None = None
263+ ) -> ConfigFlowResult :
264+ """Handle reconfiguration of the integration."""
265+ errors : dict [str , str ] = {}
266+
267+ reconfigure_entry = self ._get_reconfigure_entry ()
268+
269+ if user_input :
270+ # Redefine ingest existing username and password
271+ full_input = {
272+ CONF_HOST : user_input .get (CONF_HOST ),
273+ CONF_PORT : reconfigure_entry .data .get (CONF_PORT ),
274+ CONF_USERNAME : reconfigure_entry .data .get (CONF_USERNAME ),
275+ CONF_PASSWORD : reconfigure_entry .data .get (CONF_PASSWORD ),
276+ }
277+
278+ api , errors = await verify_connection (self .hass , full_input )
279+ if api :
280+ await self .async_set_unique_id (
281+ api .smile_hostname or api .gateway_id , raise_on_progress = False
282+ )
283+ self ._abort_if_unique_id_mismatch (reason = "not_the_same_smile" )
284+ return self .async_update_reload_and_abort (
285+ reconfigure_entry ,
286+ data_updates = full_input ,
287+ )
288+
289+ return self .async_show_form (
290+ step_id = "reconfigure" ,
291+ data_schema = self .add_suggested_values_to_schema (
292+ data_schema = SMILE_RECONF_SCHEMA ,
293+ suggested_values = reconfigure_entry .data ,
294+ ),
295+ description_placeholders = {"title" : reconfigure_entry .title },
296+ errors = errors ,
297+ )
298+
245299
246300 @staticmethod
247301 @callback
0 commit comments