4545)
4646from .data .enums import InverterType
4747from .data .solis_config import CONNECTION_METHOD , SOLIS_INVERTERS , InverterConfig , inverter_options_from_config
48+ from .helpers import hmi_version_supports_v2
4849
4950_LOGGER = logging .getLogger (__name__ )
5051
@@ -270,7 +271,7 @@ async def async_step_cloud(self, user_input=None):
270271 if self ._both_mode :
271272 # The dual entry is keyed by the Modbus serial; the cloud plant must contain it
272273 user_input = {** user_input , CONF_INVERTER_SERIAL : self ._modbus_data [CONF_INVERTER_SERIAL ]}
273- serial , inverter_id , detected_model , product_model , error = await self ._validate_cloud_config (user_input )
274+ serial , inverter_id , detected_model , product_model , detected_has_v2 , error = await self ._validate_cloud_config (user_input )
274275 if error :
275276 errors ["base" ] = error
276277 elif self ._both_mode :
@@ -282,6 +283,14 @@ async def async_step_cloud(self, user_input=None):
282283 serial ,
283284 )
284285 self ._modbus_data ["model" ] = detected_model
286+ if detected_has_v2 is not None and self ._modbus_data .get ("has_v2" ) != detected_has_v2 :
287+ _LOGGER .info (
288+ "HMI firmware version identifies TOU V2 support=%s; overriding has_v2=%s for serial %s" ,
289+ detected_has_v2 ,
290+ self ._modbus_data .get ("has_v2" ),
291+ serial ,
292+ )
293+ self ._modbus_data ["has_v2" ] = detected_has_v2
285294 self ._cloud_data = user_input
286295 self ._resolved_serial = serial
287296 self ._resolved_cloud_id = inverter_id
@@ -298,9 +307,17 @@ async def async_step_cloud(self, user_input=None):
298307 user_input .get ("model" ),
299308 serial ,
300309 )
310+ if detected_has_v2 is not None and user_input .get ("has_v2" ) != detected_has_v2 :
311+ _LOGGER .info (
312+ "HMI firmware version identifies TOU V2 support=%s; overriding has_v2=%s for serial %s" ,
313+ detected_has_v2 ,
314+ user_input .get ("has_v2" ),
315+ serial ,
316+ )
301317 data = {
302318 ** user_input ,
303319 "model" : detected_model or user_input .get ("model" ),
320+ "has_v2" : detected_has_v2 if detected_has_v2 is not None else user_input .get ("has_v2" ),
304321 CONF_INVERTER_SERIAL : serial ,
305322 CONF_CLOUD_INVERTER_ID : inverter_id ,
306323 CONF_CLOUD_PRODUCT_MODEL : product_model ,
@@ -361,10 +378,10 @@ async def async_step_dual_mode(self, user_input=None):
361378 data_schema = vol .Schema ({vol .Required ("dual_mode" , default = "failover_modbus" ): vol .In (DUAL_MODE_OPTIONS )}),
362379 )
363380
364- async def _validate_cloud_config (self , user_input ) -> tuple [str | None , str | None , str | None , str | None , str | None ]:
381+ async def _validate_cloud_config (self , user_input ) -> tuple [str | None , str | None , str | None , str | None , bool | None , str | None ]:
365382 """Check credentials against the SolisCloud API and resolve the inverter serial + cloud id.
366383
367- Returns (serial, inverter_id, detected_model, product_model, None) on success.
384+ Returns (serial, inverter_id, detected_model, product_model, detected_has_v2, None) on success.
368385 The cloud id is required alongside the serial for inverterDetail reads.
369386 """
370387 from homeassistant .helpers .aiohttp_client import async_get_clientsession
@@ -384,26 +401,36 @@ async def _validate_cloud_config(self, user_input) -> tuple[str | None, str | No
384401 records = await client .async_inverter_list (user_input [CONF_CLOUD_PLANT_ID ].strip ())
385402 except SolisCloudApiError as error :
386403 _LOGGER .warning ("SolisCloud validation failed: %s" , error )
387- return None , None , None , None , "Cannot reach the SolisCloud API with these credentials. Check the key id/secret and plant id."
404+ return None , None , None , None , None , "Cannot reach the SolisCloud API with these credentials. Check the key id/secret and plant id."
388405
389406 by_serial = {str (rec ["sn" ]).upper (): rec for rec in records if rec .get ("sn" )}
390407 if not by_serial :
391- return None , None , None , None , "No inverters found for this plant id."
408+ return None , None , None , None , None , "No inverters found for this plant id."
409+
410+ async def _resolve (serial : str , record : dict ):
411+ metadata = cloud_model_metadata (record )
412+ product_model = metadata .get ("productModel" ) or metadata .get ("model" )
413+ inverter_id = str (record .get ("id" ) or "" ) or None
414+ detected_has_v2 = None
415+ # inverterList doesn't include hmiVersionAll; that field is only in inverterDetail
416+ # (the same call cloud_retrieval.py already makes every poll cycle).
417+ try :
418+ detail = await client .async_inverter_detail (serial , inverter_id )
419+ except SolisCloudApiError as error :
420+ _LOGGER .debug ("HMI version lookup failed for serial %s during setup: %s" , serial , error )
421+ else :
422+ detected_has_v2 = hmi_version_supports_v2 (detail .get ("hmiVersionAll" ))
423+ return serial , inverter_id , infer_model_from_cloud_record (record ), product_model , detected_has_v2 , None
392424
393425 wanted = (user_input .get (CONF_INVERTER_SERIAL ) or "" ).strip ().upper ()
394426 if wanted :
395427 if wanted not in by_serial :
396- return None , None , None , None , f"Inverter serial { wanted } not found in this plant (found: { ', ' .join (by_serial )} )."
397- record = by_serial [wanted ]
398- metadata = cloud_model_metadata (record )
399- product_model = metadata .get ("productModel" ) or metadata .get ("model" )
400- return wanted , str (record .get ("id" ) or "" ) or None , infer_model_from_cloud_record (record ), product_model , None
428+ return None , None , None , None , None , f"Inverter serial { wanted } not found in this plant (found: { ', ' .join (by_serial )} )."
429+ return await _resolve (wanted , by_serial [wanted ])
401430 if len (by_serial ) == 1 :
402431 serial , record = next (iter (by_serial .items ()))
403- metadata = cloud_model_metadata (record )
404- product_model = metadata .get ("productModel" ) or metadata .get ("model" )
405- return serial , str (record .get ("id" ) or "" ) or None , infer_model_from_cloud_record (record ), product_model , None
406- return None , None , None , None , f"This plant has multiple inverters ({ ', ' .join (by_serial )} ); please enter the serial of the one to add."
432+ return await _resolve (serial , record )
433+ return None , None , None , None , None , f"This plant has multiple inverters ({ ', ' .join (by_serial )} ); please enter the serial of the one to add."
407434
408435 async def async_step_reconfigure (self , user_input = None ):
409436 """Handle reconfiguration."""
@@ -451,7 +478,7 @@ async def async_step_reconfigure_cloud(self, user_input=None):
451478 entry = self .hass .config_entries .async_get_entry (self .context ["entry_id" ])
452479
453480 if user_input is not None :
454- serial , inverter_id , detected_model , product_model , error = await self ._validate_cloud_config (user_input )
481+ serial , inverter_id , detected_model , product_model , detected_has_v2 , error = await self ._validate_cloud_config (user_input )
455482 if error :
456483 errors ["base" ] = error
457484 elif serial != entry .unique_id :
@@ -464,10 +491,18 @@ async def async_step_reconfigure_cloud(self, user_input=None):
464491 user_input .get ("model" ),
465492 serial ,
466493 )
494+ if detected_has_v2 is not None and user_input .get ("has_v2" ) != detected_has_v2 :
495+ _LOGGER .info (
496+ "HMI firmware version identifies TOU V2 support=%s; overriding has_v2=%s for serial %s" ,
497+ detected_has_v2 ,
498+ user_input .get ("has_v2" ),
499+ serial ,
500+ )
467501 data = {
468502 ** entry .data ,
469503 ** user_input ,
470504 "model" : detected_model or user_input .get ("model" ),
505+ "has_v2" : detected_has_v2 if detected_has_v2 is not None else user_input .get ("has_v2" ),
471506 CONF_INVERTER_SERIAL : serial ,
472507 CONF_CLOUD_INVERTER_ID : inverter_id ,
473508 CONF_CLOUD_PRODUCT_MODEL : product_model ,
@@ -561,6 +596,26 @@ async def _validate_config(self, user_input):
561596 else :
562597 await modbus_controller .async_read_input_register (35000 , 1 )
563598
599+ # Auto-detect TOU V2 support from HMI firmware version (register 33002) rather
600+ # than relying solely on the form's manual toggle. A failed/unparseable read
601+ # must never block setup; the user's chosen value survives untouched.
602+ try :
603+ hmi_registers = await modbus_controller .async_read_input_register (33002 , 1 )
604+ except Exception as hmi_error :
605+ _LOGGER .debug ("HMI version read failed during setup; leaving has_v2 as configured: %s" , hmi_error )
606+ else :
607+ if hmi_registers :
608+ detected_has_v2 = hmi_version_supports_v2 (hmi_registers [0 ])
609+ if detected_has_v2 is not None and user_input .get ("has_v2" ) != detected_has_v2 :
610+ _LOGGER .info (
611+ "HMI version %s identifies TOU V2 support=%s; overriding has_v2=%s for serial %s" ,
612+ hmi_registers [0 ],
613+ detected_has_v2 ,
614+ user_input .get ("has_v2" ),
615+ user_input .get (CONF_INVERTER_SERIAL ),
616+ )
617+ user_input ["has_v2" ] = detected_has_v2
618+
564619 return True , None
565620 except Exception as e :
566621 _LOGGER .warning (f"Connection failed attempt { attempt + 1 } /5: { str (e )} " )
0 commit comments