Repository navigation
fix(solax): build PV yield from the inverters, not the plant total - #5357
Merged
Merged
Conversation
Contributor
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
The normalized plant ID causes inverter lookup to fail for non-normalized SolaX IDs, silently restoring the incorrect plant-total calculation.
Review effort: Balanced
Findings: 1
Open (1)
What changed in this PR
Fixes SolaX PV/load accounting by deriving PV yield from inverter-level counters instead of the plant aggregate.
Changes:
- Adds inverter-summed PV yield with fallback and failure gating.
- Updates automatic configuration, diagnostics, documentation, and tests.
- Records the SolaX behavior in the debug journal.
| File | Description |
|---|---|
apps/predbat/solax.py |
Implements corrected PV-yield aggregation. |
apps/predbat/tests/test_solax.py |
Adds aggregation and failure-path tests. |
docs/apps-yaml.md |
Documents the new sensor. |
tools/debug-journal.md |
Records the diagnosed behavior. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
On a SolaX Cloud plant with a PV-only inverter and a separate AC-coupled battery inverter, the plant realtime totalYield includes the battery inverter's AC output. pv_today pointed at it, so battery discharge was counted as PV, counted again in total_load, and inflated PV calibration. Publish a new plant sensor, <plant>_pv_yield, summed from each inverter's totalYield, use it for pv_today and in the total_load sum, and hold both when an inverter was not read so the cumulative counter never dips. The plant figure is still used where no inverter reports a yield of its own. Fixes #5356 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
automatic_config() pointed pv_power and grid_power at the first inverter listed for the plant. On a plant with a PV-only inverter and a separate battery inverter that is one device's reading, and which one depends on the order the API returns them in. Publish plant level pv_power and grid_power sensors from the sums already built for load_power, and point the args at those. Both are held when an inverter in the plant was not read on the cycle. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…he plant get_plant_pv_yield() held the whole plant whenever any inverter was missing, which treated three different cases alike. An inverter that failed a read after being read before now contributes its last known totalYield, a lifetime counter does not go backwards so the sum cannot dip. An inverter that reads fine but has no totalYield counts as 0 rather than holding the plant for ever. The value is held only when an inverter that onlineStatus says should be answering failed its read and has never given a yield. The last known yield is seeded from the inverter's own total yield sensor, so an inverter that no longer answers stays in the sum across a restart. The plant pv_power and grid_power follow the same narrowing: an offline inverter counts as 0W rather than holding them. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…s it plant_inverters is keyed by the raw plant ID while the entity names use a lowercased form with spaces replaced. get_plant_pv_yield() and the plant power hold were handed the entity form, so a plant ID the tidying changes found no inverters: the PV yield fell back to the plant total and the plant powers were published with an inverter unread. Numeric plant IDs, which is every plant seen so far, were unaffected. Review feedback on #5357. Also records the live DC-coupled and X1-AC findings in the debug journal. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
springfall2008
force-pushed
the
fix/solax-pv-yield-from-devices-5356
branch
from
October 4, 2026 14:55
97ca706 to
5eee944
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

This is an automated draft PR generated from issue #5356 — a maintainer should review it before merging.
Fixes #5356
Summary
On a SolaX Cloud plant with a PV-only inverter plus a separate AC-coupled battery inverter, the plant realtime
totalYieldincludes the battery inverter's AC output.pv_todaypointed at that figure, so battery discharge was counted as PV, counted a second time intotal_load, and scaled the PV forecast up through calibration.sensor.<prefix>_solax_<plant>_pv_yield, the sum of each inverter's device-leveltotalYield(get_plant_pv_yield()).automatic_config()now pointspv_todayat it.total_loaduses the same figure in place of the planttotalYield.totalYield, kept per inverter and updated only when a read returns a number. A lifetime counter does not go backwards, so a failed read cannot make the sum dip, andtotal_loadkeeps being published. After a restart the last known value is seeded from the inverter's own..._<sn>_total_yieldsensor, so an inverter that no longer answers stays in the sum. An inverter that reads fine but has nototalYieldcounts as 0. Both figures are held only when an inverter thatonlineStatussays should be answering failed its read and has never given a yield; an offline inverter that never reported counts as 0...._total_yieldstill publishes the raw plant figure, so existing dashboards keep their meaning.pv_powerandgrid_powerwere taken from the first inverter listed for the plant, so on a mixed plant they were one device's reading and depended on the order the API returned the inverters in. New plant sensorssensor.<prefix>_solax_<plant>_pv_powerand..._grid_powerare summed over every inverter (the sumsload_poweralready used), andautomatic_config()points the two args at them. Both are held when an inverter that should be answering was not read on the cycle; an offline inverter counts as 0 W. The per-inverter sensors are unchanged.python3 solax.py --client-id ... --client-secret ...) now prints the plant yield, each inverter's yield and AC output counters, and the figure that would be published, for checking a live plant.Testing
./run_all --test solax: fails without the fix, passes with it. Withsolax.pystashed the module fails on the newpv_todayassertion intest_automatic_config_main(got ['sensor.predbat_solax_plant1_total_yield']); the run stops at that first failure, so the newtest_plant_pv_yield_mainwas not separately reached in the red run. With the fix all six of its cases pass: inverter sum used for PV and load, plant total unchanged, held on a failed inverter read, republished on the next good read, held for an unread inverter, fallback to the plant figure, and no fallback once the inverters have reported../run_all --quick: all tests passed../run_pre_commit: passed../run_all --test solaxfor the power change: fails without it (Expected plant pv_power 2000W summed over both inverters, got None), passes with it. Covers the sum over two inverters, the hold on a failed inverter read, the republish on the next good read, and thepv_power/grid_powerargs.solax.pythe rewrittentest_plant_pv_yield_mainfails four cases (load not republished on a failed read, a lone inverter with nototalYield, an offline inverter that never reported, the seed from the sensor); all pass with it../run_all --quickpasses on the final commit. Pre-commit was run on the changed files only for the second and third commits.pv_todaypoints at..._pv_yield, which published 10282.2 kWh, the X1-Boost's owntotalYield;..._total_yieldstill published the plant's 19596.81;total_loadmatched the formula with the inverter figure. The daily relationship held again: 9.2 + 5.3 = plantdailyYield14.5.pv_power/grid_powerpublished.pv_poweris the sum of both (the first inverter alone was 4698 W of 5280 W).totalYield: no longer held, falls back to the plant figure. See the note below, that figure is wrong on this plant../run_all --quickpasses on the rebased branch.Notes
dailyACOutput= plantdailyYield, exact on three days). The lifetime totals did not decompose the same way, and this PR makes no assumption about how SolaX builds them.dailyYield(23.3 and 2.6) anddailyACOutput(24.1 and 9.5) are separate counters, and the plantdailyYield(33.6) is exactly the sum of the two AC outputs. So the plant figure is inverter AC output there too, and the old wiring over-read PV on that plant as well (33.6 against 25.9). Device yield was not checked against an independent PV measurement.totalYieldand no MPPT or PV map, so the PV yield falls back to the plant figure, and on the live plant that figure is battery discharge (plantdailyYield= inverterdailyACOutput= plantdailyDischarged, one day's sample). This is the pre-PR behaviour, not a regression. Publishing 0 for an inverter with no PV inputs would fix it; left for a maintainer decision.plant_invertersis keyed by the plant ID as SolaX gives it and the entity names use a lowercased form. The yield and plant power lookups now use the raw ID. Numeric plant IDs, which is every plant seen, were unaffected.test_publish_plant_info_main,test_realtime_publish_gating_main) gained an inverter realtime read, since the load is now only published once the inverters have been read...._pv_yieldis a new entity starting at a different absolute value, so the day of the upgrade has a partial day of PV. Inflated PV history stays in the calibration window for several days on affected sites.state_classon the new sensor ismeasurement, matching the other plant totals rather than fixing them all here.impact()onpublish_plant_infoandautomatic_config: LOW risk, one direct caller each (run).🤖 Generated with Claude Code