@@ -267,12 +267,12 @@ async def async_read_inverter_firmware_info(hub: Any) -> int:
267267 inverter_data = await hub .async_read_holding_registers (unit = hub ._modbus_addr , address = 0x7D , count = 8 )
268268 if inverter_data is not None and not inverter_data .isError ():
269269 registers = inverter_data .registers
270- data ["firmware_dsp " ] = int (registers [0 ])
270+ data ["firmware_dsp_minor " ] = int (registers [0 ])
271271 data ["firmware_DSP_hardware_version" ] = int (registers [1 ])
272272 data ["firmware_dsp_major" ] = int (registers [2 ])
273273 data ["firmware_arm_major" ] = int (registers [3 ])
274274 version = int (registers [5 ])
275- data ["firmware_arm " ] = int (registers [6 ])
275+ data ["firmware_arm_minor " ] = int (registers [6 ])
276276 data ["bootloader_version" ] = int (registers [7 ])
277277 except Exception :
278278 _LOGGER .debug (f"{ hub .name } : attempt to read inverter firmware info failed data: { inverter_data } " , exc_info = True )
@@ -284,8 +284,8 @@ async def async_read_inverter_firmware_info(hub: Any) -> int:
284284 else :
285285 hub .modbus_protocol_version = None
286286 data .pop ("modbus_protocol_version" , None )
287- data .pop ("firmware_version_dsp " , None )
288- data .pop ("firmware_version_arm " , None )
287+ data .pop ("firmware_dsp " , None )
288+ data .pop ("firmware_arm " , None )
289289 _LOGGER .debug (f"{ hub .name } : Modbus protocol document version unavailable" )
290290 return 0
291291
@@ -294,13 +294,13 @@ async def async_read_inverter_firmware_info(hub: Any) -> int:
294294 try :
295295 full_version_data = await hub .async_read_holding_registers (unit = hub ._modbus_addr , address = 0x7B , count = 2 )
296296 if full_version_data is not None and not full_version_data .isError ():
297- data ["firmware_version_dsp " ] = int (full_version_data .registers [0 ])
298- data ["firmware_version_arm " ] = int (full_version_data .registers [1 ])
297+ data ["firmware_dsp " ] = int (full_version_data .registers [0 ])
298+ data ["firmware_arm " ] = int (full_version_data .registers [1 ])
299299 except Exception :
300300 _LOGGER .debug (f"{ hub .name } : attempt to read full firmware version failed data: { full_version_data } " , exc_info = True )
301301 else :
302- data .pop ("firmware_version_dsp " , None )
303- data .pop ("firmware_version_arm " , None )
302+ data .pop ("firmware_dsp " , None )
303+ data .pop ("firmware_arm " , None )
304304
305305 return version
306306
@@ -1325,7 +1325,8 @@ def value_function_battery_capacity_gen5(initval: int, descr: Any, datadict: dic
13251325
13261326
13271327def value_function_software_version_g2 (initval : int , descr : Any , datadict : dict [str , Any ]) -> str | None :
1328- return f"DSP v2.{ datadict .get ('firmware_dsp' )} ARM v2.{ datadict .get ('firmware_arm' )} "
1328+ # AC/HYBRID GEN2: split registers hold raw minor versions with a fixed major of 2
1329+ return f"DSP v2.{ datadict .get ('firmware_dsp_minor' )} ARM v2.{ datadict .get ('firmware_arm_minor' )} "
13291330
13301331
13311332def value_function_firmware_major_default (val : Any , default : int ) -> Any :
@@ -1335,27 +1336,9 @@ def value_function_firmware_major_default(val: Any, default: int) -> Any:
13351336def value_function_software_version_g3 (initval : int , descr : Any , datadict : dict [str , Any ]) -> str | None :
13361337 return (
13371338 f"DSP v{ value_function_firmware_major_default (datadict .get ('firmware_dsp_major' ), 3 )} ."
1338- f"{ value_str_default (datadict .get ('firmware_dsp ' ), '??' ):>02} "
1339+ f"{ value_str_default (datadict .get ('firmware_dsp_minor ' ), '??' ):>02} "
13391340 f"ARM v{ value_function_firmware_major_default (datadict .get ('firmware_arm_major' ), 3 )} ."
1340- f"{ value_str_default (datadict .get ('firmware_arm' ), '??' ):>02} "
1341- )
1342-
1343-
1344- def value_function_software_version_g4 (initval : int , descr : Any , datadict : dict [str , Any ]) -> str | None :
1345- return (
1346- f"DSP { value_str_default (datadict .get ('firmware_dsp_major' ), '?' )} ."
1347- f"{ value_str_default (datadict .get ('firmware_dsp' ), '??' ):>02} "
1348- f"ARM { value_str_default (datadict .get ('firmware_arm_major' ), '?' )} ."
1349- f"{ value_str_default (datadict .get ('firmware_arm' ), '??' ):>02} "
1350- )
1351-
1352-
1353- def value_function_software_version_g5 (initval : int , descr : Any , datadict : dict [str , Any ]) -> str | None :
1354- return (
1355- f"DSP { value_str_default (datadict .get ('firmware_dsp_major' ), '???' ):>03} ."
1356- f"{ value_str_default (datadict .get ('firmware_dsp' ), '??' ):>02} "
1357- f"ARM { value_str_default (datadict .get ('firmware_arm_major' ), '???' ):>03} ."
1358- f"{ value_str_default (datadict .get ('firmware_arm' ), '??' ):>02} "
1341+ f"{ value_str_default (datadict .get ('firmware_arm_minor' ), '??' ):>02} "
13591342 )
13601343
13611344
@@ -1366,31 +1349,68 @@ def value_function_modbus_protocol_version(datadict: dict[str, Any]) -> int:
13661349 return 0
13671350
13681351
1369- def value_function_software_version_full (initval : int , descr : Any , datadict : dict [str , Any ]) -> str | None :
1370- dsp = datadict .get ("firmware_version_dsp" )
1371- arm = datadict .get ("firmware_version_arm" )
1372- dsp_str = f"{ dsp // 100 } .{ dsp % 100 :02d} " if dsp is not None else "?.??"
1373- arm_str = f"{ arm // 100 } .{ arm % 100 :02d} " if arm is not None else "?.??"
1352+ def value_function_software_version_mic (initval : int , descr : Any , datadict : dict [str , Any ]) -> str | None :
1353+ """MIC firmware version. firmware_dsp / firmware_arm hold the full version ÷100 encoded.
1354+
1355+ GEN1 — 0x33D/0x33E GEN2 — 0x352/0x353 (+ 0x354 ARM boot, ÷100) GEN4 — 0x394/0x390
1356+ Example (X3-MIC/PRO-G2): 136 → "DSP 1.36 ARM 1.36-1.00", matching SolaX Cloud.
1357+ """
1358+ dsp = datadict .get ("firmware_dsp" ) or 0
1359+ arm = datadict .get ("firmware_arm" ) or 0
1360+ dsp_str = f"{ dsp // 100 } .{ dsp % 100 :02d} " if dsp else "?.??"
1361+ arm_str = f"{ arm // 100 } .{ arm % 100 :02d} " if arm else "?.??"
1362+ arm_boot = datadict .get ("firmware_arm_boot" )
1363+ if arm_boot is not None :
1364+ arm_str += f"-{ arm_boot // 100 } .{ arm_boot % 100 :02d} " # 0x354 is ÷100 encoded
13741365 return f"DSP { dsp_str } ARM { arm_str } "
13751366
13761367
1377- def value_function_software_version_protocol_aware (initval : int , descr : Any , datadict : dict [str , Any ]) -> str | None :
1378- # FirmwareVersionModbus 100 means V001.00; newer protocol versions can use the combined 0x7B/0x7C registers.
1379- if (
1380- value_function_modbus_protocol_version (datadict ) >= 100
1381- and datadict .get ("firmware_version_dsp" ) is not None
1382- and datadict .get ("firmware_version_arm" ) is not None
1383- ):
1384- return value_function_software_version_full (initval , descr , datadict )
1385- return value_function_software_version_g4 (initval , descr , datadict )
1368+ def value_function_software_version_hybrid_legacy (initval : int , descr : Any , datadict : dict [str , Any ]) -> str | None :
1369+ """AC/HYBRID with modbus protocol < 100: version is major.minor from split registers.
1370+
1371+ DSP = firmware_dsp_major (0x7F) . firmware_dsp_minor (0x7D)
1372+ ARM = firmware_arm_major (0x80) . firmware_arm_minor (0x83), boot suffix from
1373+ bootloader_version (0x84, raw minor) — e.g. "ARM 1.58-1.15" (SolaX Cloud).
1374+ """
1375+ dsp_maj = datadict .get ("firmware_dsp_major" )
1376+ dsp_min = datadict .get ("firmware_dsp_minor" )
1377+ arm_maj = datadict .get ("firmware_arm_major" )
1378+ arm_min = datadict .get ("firmware_arm_minor" )
1379+ dsp_str = f"{ dsp_maj } .{ dsp_min :02d} " if dsp_maj is not None and dsp_min is not None else "?.??"
1380+ arm_str = f"{ arm_maj } .{ arm_min :02d} " if arm_maj is not None and arm_min is not None else "?.??"
1381+ arm_boot = datadict .get ("bootloader_version" )
1382+ if arm_maj is not None and arm_boot is not None :
1383+ arm_str += f"-{ arm_maj } .{ arm_boot :02d} "
1384+ return f"DSP { dsp_str } ARM { arm_str } "
13861385
13871386
1388- def value_function_software_version_air_g3 (initval : int , descr : Any , datadict : dict [str , Any ]) -> str | None :
1389- return f"DSP v2. { datadict . get ( 'firmware_dsp' ) } ARM v1. { datadict . get ( 'firmware_arm' ) } "
1387+ def value_function_software_version_hybrid_full (initval : int , descr : Any , datadict : dict [str , Any ]) -> str | None :
1388+ """AC/HYBRID with modbus protocol >= 100: full version ÷100 from combined registers.
13901389
1390+ DSP = firmware_dsp (0x7B), ARM = firmware_arm (0x7C), both ÷100 encoded.
1391+ ARM boot suffix from firmware_arm_major (0x80) + bootloader_version (0x84, raw minor).
1392+ """
1393+ dsp = datadict .get ("firmware_dsp" )
1394+ arm = datadict .get ("firmware_arm" )
1395+ dsp_str = f"{ dsp // 100 } .{ dsp % 100 :02d} " if dsp else "?.??"
1396+ arm_str = f"{ arm // 100 } .{ arm % 100 :02d} " if arm else "?.??"
1397+ arm_maj = datadict .get ("firmware_arm_major" )
1398+ arm_boot = datadict .get ("bootloader_version" )
1399+ if arm_maj is not None and arm_boot is not None :
1400+ arm_str += f"-{ arm_maj } .{ arm_boot :02d} "
1401+ return f"DSP { dsp_str } ARM { arm_str } "
13911402
1392- def value_function_software_version_air_g4 (initval : int , descr : Any , datadict : dict [str , Any ]) -> str | None :
1393- return f"DSP { datadict .get ('firmware_dsp' )} ARM { datadict .get ('firmware_arm' )} "
1403+
1404+ def value_function_software_version (initval : int , descr : Any , datadict : dict [str , Any ]) -> str | None :
1405+ """AC/HYBRID dispatcher: pick the full or legacy formatter by modbus protocol version.
1406+
1407+ Protocol >= 100 exposes the combined full-version registers (0x7B/0x7C); older
1408+ firmware only has the split major/minor registers.
1409+ """
1410+ proto = datadict .get ("modbus_protocol_version" ) or 0
1411+ if proto >= 100 and datadict .get ("firmware_dsp" ) is not None :
1412+ return value_function_software_version_hybrid_full (initval , descr , datadict )
1413+ return value_function_software_version_hybrid_legacy (initval , descr , datadict )
13941414
13951415
13961416def value_function_battery_voltage_cell_difference (initval : int , descr : Any , datadict : dict [str , Any ]) -> int | float :
@@ -3851,21 +3871,21 @@ def value_function_battery_voltage_cell_difference(initval: int, descr: Any, dat
38513871 icon = "mdi:solar-power-variant" ,
38523872 ),
38533873 SolaXModbusSensorEntityDescription (
3854- key = "firmware_version_dsp " ,
3874+ key = "firmware_dsp " ,
38553875 register = 0x7B ,
38563876 allowedtypes = AC | HYBRID | GEN4 | GEN5 | GEN6 ,
38573877 modbus_min = 100 ,
38583878 internal = True ,
38593879 ),
38603880 SolaXModbusSensorEntityDescription (
3861- key = "firmware_version_arm " ,
3881+ key = "firmware_arm " ,
38623882 register = 0x7C ,
38633883 allowedtypes = AC | HYBRID | GEN4 | GEN5 | GEN6 ,
38643884 modbus_min = 100 ,
38653885 internal = True ,
38663886 ),
38673887 SolaXModbusSensorEntityDescription (
3868- key = "firmware_dsp " ,
3888+ key = "firmware_dsp_minor " ,
38693889 register = 0x7D ,
38703890 allowedtypes = AC | HYBRID ,
38713891 internal = True ,
@@ -3899,7 +3919,7 @@ def value_function_battery_voltage_cell_difference(initval: int, descr: Any, dat
38993919 icon = "mdi:information" ,
39003920 ),
39013921 SolaXModbusSensorEntityDescription (
3902- key = "firmware_arm " ,
3922+ key = "firmware_arm_minor " ,
39033923 register = 0x83 ,
39043924 allowedtypes = AC | HYBRID ,
39053925 internal = True ,
@@ -9297,7 +9317,7 @@ def value_function_battery_voltage_cell_difference(initval: int, descr: Any, dat
92979317 SolaXModbusSensorEntityDescription (
92989318 name = "Software Version" ,
92999319 key = "software_version" ,
9300- value_function = value_function_software_version_protocol_aware ,
9320+ value_function = value_function_software_version ,
93019321 allowedtypes = AC | HYBRID | GEN4 | GEN5 | GEN6 ,
93029322 entity_category = EntityCategory .DIAGNOSTIC ,
93039323 icon = "mdi:information" ,
@@ -9404,6 +9424,12 @@ def value_function_battery_voltage_cell_difference(initval: int, descr: Any, dat
94049424 allowedtypes = MIC | GEN2 ,
94059425 internal = True ,
94069426 ),
9427+ SolaXModbusSensorEntityDescription (
9428+ key = "firmware_arm_boot" ,
9429+ register = 0x354 ,
9430+ allowedtypes = MIC | GEN2 ,
9431+ internal = True ,
9432+ ),
94079433 SolaXModbusSensorEntityDescription (
94089434 key = "lock_state" ,
94099435 register = 0x367 ,
@@ -10755,38 +10781,14 @@ def value_function_battery_voltage_cell_difference(initval: int, descr: Any, dat
1075510781 SolaXModbusSensorEntityDescription (
1075610782 name = "Software Version" ,
1075710783 key = "software_version" ,
10758- value_function = value_function_software_version_air_g3 ,
10759- allowedtypes = MIC | GEN2 | X1 ,
10760- entity_category = EntityCategory .DIAGNOSTIC ,
10761- icon = "mdi:information" ,
10762- ),
10763- SolaXModbusSensorEntityDescription (
10764- name = "Software Version" ,
10765- key = "software_version" ,
10766- value_function = value_function_software_version_air_g4 ,
10767- allowedtypes = MIC | GEN4 | X1 ,
10768- entity_category = EntityCategory .DIAGNOSTIC ,
10769- icon = "mdi:information" ,
10770- ),
10771- SolaXModbusSensorEntityDescription (
10772- name = "Software Version" ,
10773- key = "software_version" ,
10774- value_function = value_function_software_version_air_g4 ,
10775- allowedtypes = MIC | GEN | X3 ,
10784+ value_function = value_function_software_version_mic ,
10785+ allowedtypes = MIC ,
1077610786 blacklist = [
1077710787 "MU802T" ,
1077810788 ],
1077910789 entity_category = EntityCategory .DIAGNOSTIC ,
1078010790 icon = "mdi:information" ,
1078110791 ),
10782- SolaXModbusSensorEntityDescription (
10783- name = "Software Version" ,
10784- key = "software_version" ,
10785- value_function = value_function_software_version_g2 ,
10786- allowedtypes = MIC | GEN2 | X3 ,
10787- entity_category = EntityCategory .DIAGNOSTIC ,
10788- icon = "mdi:information" ,
10789- ),
1079010792]
1079110793
1079210794TIME_TYPES = [
@@ -11647,8 +11649,8 @@ async def async_determineInverterType(self, hub: Any, configdict: dict[str, Any]
1164711649 hub .data ["hardware_version" ] = value_function_hardware_version_g5 (0 , None , hub .data )
1164811650 elif invertertype & GEN6 :
1164911651 hub .data ["hardware_version" ] = value_function_hardware_version_g6 (0 , None , hub .data )
11650- if "firmware_dsp" in hub .data or "firmware_version_dsp " in hub .data :
11651- hub .data ["software_version" ] = value_function_software_version_protocol_aware (0 , None , hub .data )
11652+ if "firmware_dsp" in hub .data or "firmware_dsp_minor " in hub .data :
11653+ hub .data ["software_version" ] = value_function_software_version (0 , None , hub .data )
1165211654
1165311655 read_eps = configdict .get (CONF_READ_EPS , DEFAULT_READ_EPS )
1165411656 read_dcb = configdict .get (CONF_READ_DCB , DEFAULT_READ_DCB )
0 commit comments