1- import voluptuous as vol
1+ import asyncio
22import logging
3+
4+ import voluptuous as vol
35from homeassistant import config_entries
46from homeassistant .config_entries import OptionsFlowWithConfigEntry
57
68from . import ModbusController
79from .const import (
810 DOMAIN , CONN_TYPE_TCP , CONN_TYPE_SERIAL , CONF_SERIAL_PORT ,
911 CONF_BAUDRATE , CONF_BYTESIZE , CONF_PARITY , CONF_STOPBITS ,
10- CONF_CONNECTION_TYPE , DEFAULT_BAUDRATE , DEFAULT_BYTESIZE ,
12+ CONF_CONNECTION_TYPE , CONF_INVERTER_SERIAL , DEFAULT_BAUDRATE , DEFAULT_BYTESIZE ,
1113 DEFAULT_PARITY , DEFAULT_STOPBITS
1214)
1315from .data .enums import InverterType
1416from .data .solis_config import SOLIS_INVERTERS , InverterConfig , CONNECTION_METHOD
15- import re
1617
1718_LOGGER = logging .getLogger (__name__ )
1819
3435
3536# Base schema with common fields (for both TCP and Serial)
3637BASE_CONFIG_SCHEMA = {
37- vol .Required (CONF_CONNECTION_TYPE , default = CONN_TYPE_SERIAL ): vol .In (CONNECTION_TYPES ),
38+ vol .Required (CONF_CONNECTION_TYPE , default = CONN_TYPE_TCP ): vol .In (CONNECTION_TYPES ),
39+ vol .Required (CONF_INVERTER_SERIAL ): str ,
3840 vol .Required ("slave" , default = 1 ): int ,
3941 vol .Optional ("poll_interval_fast" , default = 10 ): vol .All (int , vol .Range (min = 10 )),
4042 vol .Optional ("poll_interval_normal" , default = 15 ): vol .All (int , vol .Range (min = 15 )),
4143 vol .Optional ("poll_interval_slow" , default = 30 ): vol .All (int , vol .Range (min = 30 )),
4244 vol .Required ("model" , default = list (SOLIS_MODELS .keys ())[0 ]): vol .In (SOLIS_MODELS ),
43- vol .Optional ("identification" , default = "" ): str ,
4445 # Boolean options (Yes/No toggle)
4546 vol .Required ("has_v2" , default = True ): vol .Coerce (bool ),
4647 vol .Required ("has_pv" , default = True ): vol .Coerce (bool ),
101102)
102103
103104
104- def clean_identification (iden : str | None ) -> str | None :
105- if not iden or not iden .strip ():
106- return None
107- # Replace spaces and disallowed characters with underscores
108- iden = iden .strip ().lower ()
109- iden = re .sub (r"[^a-z0-9_]" , "_" , iden )
110- return re .sub (r"_+" , "_" , iden ).strip ("_" )
111-
112-
113105class ModbusConfigFlow (config_entries .ConfigFlow , domain = DOMAIN ):
114106 """Modbus configuration flow."""
115107
@@ -137,7 +129,7 @@ async def async_step_user(self, user_input=None):
137129 return self .async_show_form (
138130 step_id = "user" ,
139131 data_schema = vol .Schema ({
140- vol .Required (CONF_CONNECTION_TYPE , default = CONN_TYPE_SERIAL ): vol .In (CONNECTION_TYPES ),
132+ vol .Required (CONF_CONNECTION_TYPE , default = CONN_TYPE_TCP ): vol .In (CONNECTION_TYPES ),
141133 }),
142134 errors = errors
143135 )
@@ -155,12 +147,12 @@ async def async_step_config(self, user_input=None):
155147 if self ._connection_type == CONN_TYPE_TCP :
156148 # Create TCP schema without connection_type field
157149 schema_dict = {k : v for k , v in TCP_CONFIG_SCHEMA .items ()
158- if not (hasattr (k , 'schema' ) and k .schema == CONF_CONNECTION_TYPE )}
150+ if not (hasattr (k , 'schema' ) and k .schema == CONF_CONNECTION_TYPE )}
159151 schema = vol .Schema (schema_dict )
160152 else :
161153 # Create Serial schema without connection_type field
162154 schema_dict = {k : v for k , v in SERIAL_CONFIG_SCHEMA .items ()
163- if not (hasattr (k , 'schema' ) and k .schema == CONF_CONNECTION_TYPE )}
155+ if not (hasattr (k , 'schema' ) and k .schema == CONF_CONNECTION_TYPE )}
164156 schema = vol .Schema (schema_dict )
165157
166158 return self .async_show_form (
@@ -169,6 +161,50 @@ async def async_step_config(self, user_input=None):
169161 errors = errors
170162 )
171163
164+ async def async_step_reconfigure (self , user_input = None ):
165+ """Handle reconfiguration."""
166+ errors = {}
167+ entry = self .hass .config_entries .async_get_entry (self .context ["entry_id" ])
168+
169+ if user_input is not None :
170+ data = {** entry .data , ** user_input }
171+
172+ if await self ._validate_config (data ):
173+ return self .async_update_reload_and_abort (
174+ entry , data = data
175+ )
176+
177+ errors ["base" ] = "Cannot connect to Modbus device. Please check your configuration."
178+
179+ # Show config form with existing values pre-filled
180+ # Determine schema based on existing connection type
181+ conn_type = entry .data .get (CONF_CONNECTION_TYPE , CONN_TYPE_TCP )
182+
183+ if conn_type == CONN_TYPE_TCP :
184+ schema_dict = TCP_CONFIG_SCHEMA .copy ()
185+ else :
186+ schema_dict = SERIAL_CONFIG_SCHEMA .copy ()
187+
188+ # Re-create schema with defaults from entry data
189+ new_schema = {}
190+ for key , value in schema_dict .items ():
191+ if key in entry .data :
192+ # If key exists in data, use it as default
193+ new_schema [key ] = vol .Required (key , default = entry .data [key ]) if isinstance (value ,
194+ vol .Required ) else vol .Optional (
195+ key , default = entry .data [key ])
196+ elif key == CONF_INVERTER_SERIAL and CONF_INVERTER_SERIAL not in entry .data :
197+ # If serial is missing (the problem we are solving), make it required without default (or empty string)
198+ new_schema [key ] = vol .Required (key )
199+ else :
200+ new_schema [key ] = value
201+
202+ return self .async_show_form (
203+ step_id = "reconfigure" ,
204+ data_schema = vol .Schema (new_schema ),
205+ errors = errors
206+ )
207+
172208 async def _create_entry_from_input (self , user_input ):
173209 """Create config entry from validated input."""
174210 conn_type = user_input .get (CONF_CONNECTION_TYPE , CONN_TYPE_SERIAL )
@@ -181,10 +217,10 @@ async def _create_entry_from_input(self, user_input):
181217 # Multi-step flow - return to config step without connection_type field
182218 if conn_type == CONN_TYPE_TCP :
183219 schema_dict = {k : v for k , v in TCP_CONFIG_SCHEMA .items ()
184- if not (hasattr (k , 'schema' ) and k .schema == CONF_CONNECTION_TYPE )}
220+ if not (hasattr (k , 'schema' ) and k .schema == CONF_CONNECTION_TYPE )}
185221 else :
186222 schema_dict = {k : v for k , v in SERIAL_CONFIG_SCHEMA .items ()
187- if not (hasattr (k , 'schema' ) and k .schema == CONF_CONNECTION_TYPE )}
223+ if not (hasattr (k , 'schema' ) and k .schema == CONF_CONNECTION_TYPE )}
188224
189225 return self .async_show_form (
190226 step_id = "config" ,
@@ -228,7 +264,6 @@ async def _validate_config(self, user_input):
228264 controller_params = {
229265 "hass" : self .hass ,
230266 "device_id" : user_input .get ("slave" , 1 ),
231- "identification" : clean_identification (user_input .get ("identification" , None )),
232267 "fast_poll" : user_input .get ("poll_interval_fast" , 10 ),
233268 "normal_poll" : user_input .get ("poll_interval_normal" , 15 ),
234269 "slow_poll" : user_input .get ("poll_interval_slow" , 15 ),
@@ -248,19 +283,25 @@ async def _validate_config(self, user_input):
248283
249284 modbus_controller = ModbusController (** controller_params )
250285
251- try :
252- if not await modbus_controller .connect ():
253- raise ConnectionError ("Failed to connect" )
254- if inverter_config .type in [InverterType .GRID , InverterType .STRING ]:
255- await modbus_controller .async_read_input_register (3041 , 1 )
256- else :
257- await modbus_controller .async_read_input_register (35000 , 1 )
258- return True
259- except Exception as e :
260- _LOGGER .error (f"Connection failed: { str (e )} " )
261- return False
262- finally :
263- modbus_controller .close_connection ()
286+ for attempt in range (5 ):
287+ try :
288+ if not await modbus_controller .connect ():
289+ raise ConnectionError ("Failed to connect" )
290+ if inverter_config .type in [InverterType .GRID , InverterType .STRING ]:
291+ await modbus_controller .async_read_input_register (3041 , 1 )
292+ else :
293+ await modbus_controller .async_read_input_register (35000 , 1 )
294+
295+ return True
296+ except Exception as e :
297+ _LOGGER .warning (f"Connection failed attempt { attempt + 1 } /5: { str (e )} " )
298+ if attempt < 4 :
299+ await asyncio .sleep (1 )
300+ finally :
301+ modbus_controller .close_connection ()
302+
303+ _LOGGER .error (f"Connection failed after 5 attempts: { str (controller_params )} " )
304+ return False
264305
265306 def _get_user_schema (self , user_input = None ):
266307 """Return the appropriate schema based on connection type selection."""
0 commit comments