Skip to content

Commit 8d7e451

Browse files
authored
Merge pull request #2064 from Bl00d-B0b/fix/firmware-version-unified
fix(solax): unify firmware version display and key naming across all inverter types
2 parents 065919a + 1315534 commit 8d7e451

1 file changed

Lines changed: 82 additions & 80 deletions

File tree

custom_components/solax_modbus/plugin_solax.py

Lines changed: 82 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -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

13271327
def 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

13311332
def 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:
13351336
def 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

13961416
def 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

1079210794
TIME_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

Comments
 (0)