Repository navigation
fix(solax): treat a null battery chargeDischargePower as 0 in the plant sum - #5894
springfall2008 wants to merge 2 commits into
Conversation
…nt sum
get_charge_discharge_power_battery() summed .get("chargeDischargePower", 0),
whose default only covers a missing key. A battery pack that returns an
explicit null raised TypeError on every poll, leaving the SolaX component
in error with no plant data reaching the plan.
Fixes #5893
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
springfall2008
left a comment
There was a problem hiding this comment.
Automated comment from the triage bot.
Review of head 2d09d20: 1 finding, inline. The fix itself is correct: or 0 behaves identically to the old default for a missing key and for 0/negative values, and only changes the crash case. Test 16 is red without the fix (the caught TypeError makes result16 a non-number, so failed is set) and green with it (1500 W), by reading; it sits inside test_helper_methods_main(), which run_solax_tests() already calls (test_solax.py:175). The per-device guard the body cites is real (solax.py:2569). No user-facing setting or value changed for healthy data, so no docs change is needed. The one finding is inline: the same null class is still unguarded on the sibling reads of the same dict, two of which the same caller reaches before this line.
| total_power = 0 | ||
| for device_id in self.plant_batteries.get(plant_id, []): | ||
| total_power += self.realtime_device_data.get(device_id, {}).get("chargeDischargePower", 0) | ||
| total_power += self.realtime_device_data.get(device_id, {}).get("chargeDischargePower") or 0 |
There was a problem hiding this comment.
Automated comment from the triage bot.
The guard is right here, but the claim in the PR body that only the plant-level power sum is missing it is not quite true: the sibling reads of the same realtime_device_data dict on the same call path are still unguarded, and two of them run before this line does. publish_plant_info() calls get_current_soc_battery_kwh() at solax.py:2732 and get_battery_temperature() at solax.py:2736, so a null batterySOC, batteryRemainings or batteryTemperature in the same payload raises the same TypeError at solax.py:833-834 (current_soc += ...get("batterySOC", 0), battery_remainings += ...get("batteryRemainings", 0)) or solax.py:844 (min(None, temperature)), cancelling this cycle the same way.
What I verified vs. what I could not: the response is stored unfiltered (self.realtime_device_data[sn] = result[0], solax.py:1961), so an explicit null in any field lands as None, and get_current_soc_battery_kwh() has five further call sites (solax.py:818, 883, 3188, 3250, 3286) that spread the same crash class beyond the plant sensors. I could not confirm SolaX actually returns null for those other fields — the #5893 site's traceback reaching line 824 shows its SOC/temperature were numeric at that time — so this may never fire in practice; it is the same latent class rather than an observed failure.
A general fix would be to normalise the known-numeric fields where the response is stored (solax.py:1961) so every reader is safe, or failing that, apply this same guard at solax.py:833-834 and 844.
There was a problem hiding this comment.
Fixed in 11a9feb. The PR body was wrong: the SOC/remainings sum and the temperature min() share the same pattern and run first in publish_plant_info(). Both now treat an explicit null exactly like a missing field, so no existing result changes. Temperature uses an is not None check rather than or, so a real 0.0 °C reading still counts. Tests 17 and 18 fail without the change and pass with it.
The 30 Oct payload from the #5893 site confirms your caveat: only chargeDischargePower is null among the fields we read (batterySOC 97, batteryRemainings 14.8, batteryTemperature 16.0). The null totalDeviceDischarge/totalDeviceCharge are not read anywhere, and batterySOH 0 is already mapped to 1.0.
I have left out normalising at the store (solax.py:1961): it is a wider change with no observed failure behind it, so it is better as its own PR if wanted. That is noted in the PR body.
…issing fields The plant SOC sum and the battery temperature minimum read the same realtime_device_data as the power sum, earlier in publish_plant_info(), and also only defaulted a missing key. An explicit null now behaves exactly as a missing field already did. Temperature is checked against None rather than with `or`, so a real 0.0 reading is still used. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fixes #5893
Summary
get_charge_discharge_power_battery()summed each battery'schargeDischargePowerwith.get("chargeDischargePower", 0). That default only covers a missing key, so a battery pack that returns an explicitnullraisedTypeError: unsupported operand type(s) for +=: 'int' and 'NoneType'on every poll. The caller,publish_plant_info(), then left the SolaX component in error with no plant data reaching the plan.The same pattern sits in the two helpers
publish_plant_info()calls just before it,get_current_soc_battery_kwh()(batterySOC,batteryRemainings) andget_battery_temperature()(min()againstNone), so those are guarded too. In all three, an explicit null now behaves exactly as a missing field already did, so no existing result changes. Temperature is checked againstNonerather than withor, so a real 0.0 °C reading is still used.The per-device path in
publish_device_realtime_data()already guarded the power field.Evidence
Realtime payload for the affected pack, from the site's log on 30 Oct 2026 (serial redacted):
Only
chargeDischargePoweris an observed failure. Null SOC, remainings and temperature have not been seen from SolaX; those guards cover the same latent class on the same call path. The other nulls in the payload are harmless:totalDeviceDischarge/totalDeviceChargeare not read anywhere, andbatterySOH0 is already mapped to 1.0.Testing
None. Fails without the fix with the productionTypeError, passes with it.+=onNone, and'<' not supported between 'float' and 'NoneType'); with it, null fields give the same result as missing ones, and a null temperature is skipped../run_all --test solax: passed../run_all --quick: all tests passed (4 slow tests skipped), on each commit.Not in this PR
Normalising the known-numeric fields where the response is stored (
solax.py:1961) would protect every reader, including the furtherget_current_soc_battery_kwh()call sites. That is a wider change with no observed failure behind it, so it is left for a separate PR if wanted.🤖 Generated with Claude Code