Skip to content

fix(inverter): stop re-committing a stable export window every cycle (Solis) - #4711

Open
chalfontchubby wants to merge 3 commits into
mainfrom
fix/solis-redundant-button-press-4709
Open

fix(inverter): stop re-committing a stable export window every cycle (Solis)#4711
chalfontchubby wants to merge 3 commits into
mainfrom
fix/solis-redundant-button-press-4709

Conversation

@chalfontchubby

@chalfontchubby chalfontchubby commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator

Fixes #4709

Problem

On Solis GS_fb00 (solax_modbus), pressing the charge/discharge update button zeroes the timed charge/discharge current registers. The reporter measured the current register being knocked to 0 A at :xx:22 on every 5-minute boundary throughout an active export window, stopping export until something restored it.

The cause is in adjust_force_export. For H M time format the discharge slot-time registers are rewritten unconditionally every cycle — a deliberate write-reliability workaround from #1529. #4000 then reused that same "we wrote something" flag (changed_start_end) to decide whether to press the update button, so the button fired every cycle even when the window had not changed.

#4000's stated premise was that adjust_charge_window "always re-writes time entities for H M format → button press fires every cycle". The write half is correct; the button-press half is not — the charge side's press is gated on a plain dirty check and always has been. So the every-cycle press was never symmetric with anything.

The same flag also gates the 30 second GivTCP settle sleep in adjust_inverter_mode and the export-slot notification, both of which were therefore firing every cycle too.

Fix

Separate "we rewrote the registers" from "the schedule needs committing", and gate the press, the sleep and the notify on the latter.

Tracking the last committed schedule keeps a stable window quiet while still committing once after a restart, when nothing has been committed yet — so #4000's own regression test passes unchanged. The commit-once safety net is scoped to the H M path; every other format already commits on a real change alone.

A commit is only recorded once it has actually succeeded. press_and_poll_button returns a success flag, and discarding it would mean a failed press was recorded as done and never retried — the schedule sitting uncommitted until it happened to change on its own. The behaviour being replaced retried implicitly, because changed_start_end fired every cycle, so gating on the real result preserves that retry without bringing back the every-cycle press. (Same failure shape as the known call_service_template issue, where a service call is marked done before its success is checked.)

The register writes themselves are untouched.

Also fixes idle-cycle presses on plain GS

Beyond the export window, this PR also stops a second symptom that only came to light during triage of #4712.

GS (pre-FB00 Solis) is charge_time_format: "H M", so changed_start_end is set unconditionally there too — which means on current main it presses the update button on every idle cycle as well, all day, not just during export. Dropping changed_start_end from the commit condition fixes that. Measured over four idle adjust_force_export(False) calls:

             GS      GS_fb00
main         4/4     4/4
+#4711       1       4/4     <- this PR
+#4713       1       1

GS_fb00 and the cloud types reach the same press by a second, independent route (a None time comparison) which this PR does not touch — that is #4712, fixed by #4713 stacked on top of this one.

Also included

press_and_poll_button now presses only the side that changed. Where separate charge_update_button / discharge_update_button entities are configured, both were pressed whenever either side had something to send — unchanged since the original split-button support. Halves the presses for those users; no effect on the combined-button setups the Solis template ships by default. This was written for #2328 and parked as too small to be worth landing alone.

Commits

9a3a0e23 press_and_poll_button only presses the button(s) that changed
be3d225f commit a stable export window once, not every cycle
4499357c only record an export commit that actually succeeded

Testing

  • New regression test: a stable export window commits once, stays quiet on subsequent cycles, and commits again on a genuine change. Verified to fail without the fix.
  • New regression test: a failed button press is retried on the next cycle rather than recorded as committed. Verified to fail without 4499357c.
  • Existing Fix Solis GS_fb00: always write discharge time entities and press button during force export #4000 regression test passes unchanged.
  • Existing adjust_force_export1 passes unchanged, which is what pins the GE path's behaviour.
  • Side-scoping test from the parked work included.
  • Full --quick suite green.

Not yet confirmed against a live install — logs requested on #4709.

🤖 Generated with Claude Code

chalfontchubby and others added 2 commits August 24, 2026 17:55
…changed

Addresses #2328. press_and_poll_button()'s "separate buttons" fallback
(charge_update_button/discharge_update_button, used by e.g. some Solis and
SolaX SX4 configs instead of a single combined button) pressed both buttons
on every call, regardless of which side actually needed updating - a
charge-only window change also pressed the unrelated discharge button, and
vice versa. rszemeti's report showed exactly this: both update_charge_times
and update_discharge_times pressed together even when the charge window's
own start/end times were provably unchanged (every underlying write
correctly logged "No write needed"). He disabled Predbat after reaching
~75k inverter writes in a year to avoid EEPROM wear.

Added a `side` parameter ("charge"/"discharge"/"both", default "both" for
any caller not yet updated) and passed the correct side from each of the
four call sites (adjust_battery_target, adjust_force_export,
disable_charge_window, adjust_charge_window). A single combined button
(schedule_write_button or charge_discharge_update_button) is unaffected -
there's only one button either way.

New test_press_and_poll_button_side_scoping isolates the branching logic
by stubbing _press_single_button_and_poll (its own retry/timestamp-polling
behaviour is a separate concern), asserting each side presses only its own
button. No prior coverage existed for the separate-buttons path at all.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
For H M format inverters the discharge slot-time registers are rewritten on
every cycle as a write-reliability workaround (#1529), so changed_start_end is
True on every cycle of an unchanged export window. #4000 then used that flag to
gate the update button press, which made the button fire every 5 minutes for the
whole window.

On Solis each press zeroes the timed charge/discharge current registers, so the
inverter stops exporting until something restores them (#4709). The same flag
also triggers the 30 second GivTCP settle sleep in adjust_inverter_mode, and the
export-slot notification, on every cycle.

Separate "we rewrote the registers" from "the schedule needs committing" and
gate the press, the sleep and the notify on the latter. Tracking the last
committed schedule keeps a stable window quiet while still committing once after
a restart, when nothing has been committed yet - so #4000's own regression test
passes unchanged. The extra commit-once safety net is scoped to the H M path;
every other format already commits on a real change alone.

The register writes themselves are unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
press_and_poll_button returns a success flag that was being discarded, so a
failed button press was still recorded in last_export_schedule_committed and
never retried - the schedule would sit uncommitted until it happened to change
on its own.

The behaviour being replaced retried implicitly, because changed_start_end fired
every cycle. Gating on the real result keeps that retry without bringing back the
every-cycle press.

Same failure shape as the known call_service_template issue, where a service call
is marked done before its success is checked.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.

Slot time registers are rewritten every cycle even when unchanged, which zeroes the timed current registers on Solis

1 participant