1010from .const import (
1111 CONF_BAUDRATE ,
1212 CONF_BYTESIZE ,
13+ CONF_CLOUD_DETECTED_MODEL ,
1314 CONF_CLOUD_INVERTER_ID ,
1415 CONF_CLOUD_KEY_ID ,
1516 CONF_CLOUD_KEY_SECRET ,
1617 CONF_CLOUD_PASSWORD ,
1718 CONF_CLOUD_PLANT_ID ,
1819 CONF_CLOUD_POLL_INTERVAL ,
20+ CONF_CLOUD_PRODUCT_MODEL ,
1921 CONF_CLOUD_USERNAME ,
2022 CONF_CONNECTION_TYPE ,
2123 CONF_FAILOVER_PRIMARY ,
@@ -185,6 +187,8 @@ def __init__(self):
185187 self ._cloud_data = None
186188 self ._resolved_serial = None
187189 self ._resolved_cloud_id = None
190+ self ._cloud_product_model = None
191+ self ._cloud_detected_model = None
188192
189193 async def async_step_user (self , user_input = None ):
190194 """Handle initial step - ask for connection type."""
@@ -266,21 +270,41 @@ async def async_step_cloud(self, user_input=None):
266270 if self ._both_mode :
267271 # The dual entry is keyed by the Modbus serial; the cloud plant must contain it
268272 user_input = {** user_input , CONF_INVERTER_SERIAL : self ._modbus_data [CONF_INVERTER_SERIAL ]}
269- serial , inverter_id , error = await self ._validate_cloud_config (user_input )
273+ serial , inverter_id , detected_model , product_model , error = await self ._validate_cloud_config (user_input )
270274 if error :
271275 errors ["base" ] = error
272276 elif self ._both_mode :
277+ if detected_model and self ._modbus_data .get ("model" ) != detected_model :
278+ _LOGGER .info (
279+ "SolisCloud model metadata identifies %s; overriding selected model %s for serial %s" ,
280+ detected_model ,
281+ self ._modbus_data .get ("model" ),
282+ serial ,
283+ )
284+ self ._modbus_data ["model" ] = detected_model
273285 self ._cloud_data = user_input
274286 self ._resolved_serial = serial
275287 self ._resolved_cloud_id = inverter_id
288+ self ._cloud_product_model = product_model
289+ self ._cloud_detected_model = detected_model
276290 return await self .async_step_dual_mode ()
277291 else :
278292 await self .async_set_unique_id (serial )
279293 self ._abort_if_unique_id_configured ()
294+ if detected_model and user_input .get ("model" ) != detected_model :
295+ _LOGGER .info (
296+ "SolisCloud model metadata identifies %s; overriding selected model %s for serial %s" ,
297+ detected_model ,
298+ user_input .get ("model" ),
299+ serial ,
300+ )
280301 data = {
281302 ** user_input ,
303+ "model" : detected_model or user_input .get ("model" ),
282304 CONF_INVERTER_SERIAL : serial ,
283305 CONF_CLOUD_INVERTER_ID : inverter_id ,
306+ CONF_CLOUD_PRODUCT_MODEL : product_model ,
307+ CONF_CLOUD_DETECTED_MODEL : detected_model ,
284308 CONF_CONNECTION_TYPE : CONN_TYPE_CLOUD ,
285309 CONF_PROTOCOL_MODE : PROTO_CLOUD_ONLY ,
286310 }
@@ -325,6 +349,8 @@ async def async_step_dual_mode(self, user_input=None):
325349 ** self ._cloud_data ,
326350 CONF_INVERTER_SERIAL : serial ,
327351 CONF_CLOUD_INVERTER_ID : self ._resolved_cloud_id ,
352+ CONF_CLOUD_PRODUCT_MODEL : self ._cloud_product_model ,
353+ CONF_CLOUD_DETECTED_MODEL : self ._cloud_detected_model ,
328354 CONF_PROTOCOL_MODE : protocol_mode ,
329355 CONF_FAILOVER_PRIMARY : primary ,
330356 }
@@ -335,15 +361,16 @@ async def async_step_dual_mode(self, user_input=None):
335361 data_schema = vol .Schema ({vol .Required ("dual_mode" , default = "failover_modbus" ): vol .In (DUAL_MODE_OPTIONS )}),
336362 )
337363
338- async def _validate_cloud_config (self , user_input ) -> tuple [str | None , str | None , str | None ]:
364+ async def _validate_cloud_config (self , user_input ) -> tuple [str | None , str | None , str | None , str | None , str | None ]:
339365 """Check credentials against the SolisCloud API and resolve the inverter serial + cloud id.
340366
341- Returns (serial, inverter_id, None) on success or (None, None, error_message ) on failure .
367+ Returns (serial, inverter_id, detected_model, product_model, None ) on success .
342368 The cloud id is required alongside the serial for inverterDetail reads.
343369 """
344370 from homeassistant .helpers .aiohttp_client import async_get_clientsession
345371
346372 from .cloud .api_client import SolisCloudApiClient , SolisCloudApiError
373+ from .cloud .model_detection import cloud_model_metadata , infer_model_from_cloud_record
347374
348375 client = SolisCloudApiClient (
349376 session = async_get_clientsession (self .hass ),
@@ -357,21 +384,26 @@ async def _validate_cloud_config(self, user_input) -> tuple[str | None, str | No
357384 records = await client .async_inverter_list (user_input [CONF_CLOUD_PLANT_ID ].strip ())
358385 except SolisCloudApiError as error :
359386 _LOGGER .warning ("SolisCloud validation failed: %s" , error )
360- return None , None , "Cannot reach the SolisCloud API with these credentials. Check the key id/secret and plant id."
387+ return None , None , None , None , "Cannot reach the SolisCloud API with these credentials. Check the key id/secret and plant id."
361388
362- by_serial = {str (rec ["sn" ]).upper (): str ( rec . get ( "id" ) or "" ) for rec in records if rec .get ("sn" )}
389+ by_serial = {str (rec ["sn" ]).upper (): rec for rec in records if rec .get ("sn" )}
363390 if not by_serial :
364- return None , None , "No inverters found for this plant id."
391+ return None , None , None , None , "No inverters found for this plant id."
365392
366393 wanted = (user_input .get (CONF_INVERTER_SERIAL ) or "" ).strip ().upper ()
367394 if wanted :
368395 if wanted not in by_serial :
369- return None , None , f"Inverter serial { wanted } not found in this plant (found: { ', ' .join (by_serial )} )."
370- return wanted , by_serial [wanted ] or None , None
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
371401 if len (by_serial ) == 1 :
372- serial , inverter_id = next (iter (by_serial .items ()))
373- return serial , inverter_id or None , None
374- return None , None , f"This plant has multiple inverters ({ ', ' .join (by_serial )} ); please enter the serial of the one to add."
402+ 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."
375407
376408 async def async_step_reconfigure (self , user_input = None ):
377409 """Handle reconfiguration."""
@@ -419,17 +451,27 @@ async def async_step_reconfigure_cloud(self, user_input=None):
419451 entry = self .hass .config_entries .async_get_entry (self .context ["entry_id" ])
420452
421453 if user_input is not None :
422- serial , inverter_id , error = await self ._validate_cloud_config (user_input )
454+ serial , inverter_id , detected_model , product_model , error = await self ._validate_cloud_config (user_input )
423455 if error :
424456 errors ["base" ] = error
425457 elif serial != entry .unique_id :
426458 errors ["base" ] = f"Serial { serial } does not match this entry ({ entry .unique_id } ). Add a new entry for a different inverter."
427459 else :
460+ if detected_model and user_input .get ("model" ) != detected_model :
461+ _LOGGER .info (
462+ "SolisCloud model metadata identifies %s; overriding selected model %s for serial %s" ,
463+ detected_model ,
464+ user_input .get ("model" ),
465+ serial ,
466+ )
428467 data = {
429468 ** entry .data ,
430469 ** user_input ,
470+ "model" : detected_model or user_input .get ("model" ),
431471 CONF_INVERTER_SERIAL : serial ,
432472 CONF_CLOUD_INVERTER_ID : inverter_id ,
473+ CONF_CLOUD_PRODUCT_MODEL : product_model ,
474+ CONF_CLOUD_DETECTED_MODEL : detected_model ,
433475 }
434476 return self .async_update_reload_and_abort (entry , data = data )
435477
0 commit comments