Skip to content

fix(inverter): pad create_missing_arg's sibling slots with None, not the default (#5359) - #5491

Draft
chalfontchubby wants to merge 3 commits into
mainfrom
fix/mixed-inverter-rate-own-entry-5359
Draft

chalfontchubby wants to merge 3 commits into
mainfrom
fix/mixed-inverter-rate-own-entry-5359

Conversation

@chalfontchubby

@chalfontchubby chalfontchubby commented Oct 11, 2026 •

Copy link
Copy Markdown
Collaborator

Draft PR written by Claude on Rik's behalf, for maintainer review.

Fixes #5359

Summary

#5542 (rate_controls()) already fixed the main part of #5359: each inverter now picks its charge/discharge rate control from its own entry. This PR now fixes only the gap that remains, in create_missing_arg().

When an inverter type lacks a control, create_missing_arg() builds the per-inverter list for it. If the key was missing from args entirely, it filled all four slots with the default value, then replaced only the calling inverter's own slot with its dummy entity. Any other inverter's slot was left holding a bare number such as 2600.0, and rate_controls() reads that as a bound watts entity.

Concrete case, covered by the test: a Solis GS (current-controlled, so it builds charge_rate) as inverter 0, next to a percent-only GE Cloud unit as inverter 1. On main, inverter 1's rate_controls("charge_rate") returns (True, True), so it writes a rate to "entity" 2600.0 as well as to its percent entity. With this change it returns (False, True).

The fix pads every slot except the caller's with None, which is what the short-list branch of the same function already does.

Reach: this is not rate-specific. create_missing_arg() builds the dummy-entity list for every argument an inverter type lacks (scheduled_charge_enable, scheduled_discharge_enable, reserve, charge_limit, charge_rate, discharge_rate, inverter_mode, the four charge/discharge start/end times and the two idle times). For all of them, a sibling inverter's slot now reads None and falls back to that inverter's own default, instead of carrying inverter 0's default value as if it were an entity id.

Testing

test_create_missing_arg_does_not_fill_sibling_entries (in test_inverter.py):

  • builds a GS inverter 0 on a two-inverter fleet and checks that every argument it turned into a per-inverter list leaves inverter 1's slot None;
  • builds a percent-only GE Cloud inverter 1 beside it and checks that it uses only its percent control.

On main's inverter.py the test fails for every created argument (for example charge_rate siblings [2600.0, 2600.0, 2600.0], charge_limit [100, 100, 100]), and inverter 1 reports (True, True). With the change it passes. ./run_pre_commit (all hooks plus the quick suite) passes.

History: this PR originally also changed the rate read/write gates. Those changes and their two tests were dropped in the merge with main, because #5542's rate_controls() and test_rate_control_is_per_inverter cover them.

🤖 Generated with Claude Code

chalfontchubby and others added 3 commits October 11, 2026 21:12
… own entry (#5359)

get_current_charge_rate()/get_current_discharge_rate() picked the percent-vs-watts
path from whether "charge_rate_percent"/"discharge_rate_percent" existed ANYWHERE in
args, not from this inverter's own entry. On a mixed fleet where one inverter uses
percent control and another uses watts, the watts-only inverter's unset percent slot
quietly resolved to the 100% default, so it always read back battery_rate_max_raw
regardless of what was last written - the #3311 fault for a script-driven inverter.
adjust_charge_rate()/adjust_discharge_rate() had the same bug on the write side: both
registers were written whenever either key existed anywhere, so the watts-only
inverter tried to write its own (unset) percent entity every cycle, logging a
spurious "No entity_id for ..." warning and pinning had_errors onto the status sensor.

Also fixes create_missing_arg(): when a key was entirely absent from args, it filled
all four per-inverter slots with the same default value rather than padding every
slot but the caller's own with None (as the already-existing short-list branch does).
On a mixed fleet that left a plain number in a sibling inverter's slot before that
inverter was even constructed, which the per-entry fix above would otherwise
misread as a configured entity.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…very create_missing_arg list (#5359)

The rate-path tests set the percent arg to a bare value, which is not how a
mixed fleet reaches args; they now use a list with a configured sibling slot.
The create_missing_arg test now checks every arg constructing a GS inverter
builds, since the None padding applies to all of them, not just the rates.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Main's rate_controls() (#5542) already chooses the rate control per inverter
entry, so this branch's own read/write gates are dropped in favour of it,
along with the two tests main's test_rate_control_is_per_inverter covers.
What remains is the create_missing_arg() padding: without it a sibling's
leftover default number still reads as a bound watts entity to
rate_controls(), now proven in the test (GS beside a percent-only GEC).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chalfontchubby chalfontchubby changed the title Mixed inverter setups: choose the charge/discharge rate path from this inverter's own entry (#5359) fix(inverter): pad create_missing_arg's sibling slots with None, not the default (#5359) Oct 17, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Mixed inverter setups: the charge/discharge rate is read and written by "is the key set", not by each inverter's own entry

1 participant