Skip to content

fix(alphaess): let a same-cycle target SoC correction past the write pacer - #4776

Merged
springfall2008 merged 1 commit into
mainfrom
fix/alphaess-stale-charge-limit-4769
Aug 27, 2026
Merged

fix(alphaess): let a same-cycle target SoC correction past the write pacer#4776
springfall2008 merged 1 commit into
mainfrom
fix/alphaess-stale-charge-limit-4769

Conversation

@springfall2008

Copy link
Copy Markdown
Owner

This is an automated draft PR generated from issue #4769 — a maintainer should review it before merging.

Fixes #4769

Summary

Predbat commits an AlphaESS schedule in stages — charge window, then the enable switch, then the target SoC — pressing the schedule write button after each one (INVERTER_DEF time_button_press). The first commit of a cycle therefore carries whatever target SoC the control entity still holds from the previous cycle, and alphaess_min_write_interval then held the corrected value that arrived ~3 seconds later for the full 300s. On the reporter's manual charge for the already-live slot that meant AlphaESS ran a chargeLimit: 10 schedule while the battery sat at 69%, with the house on the grid.

_write_allowed() now exempts one thing: a correction landing within ALPHAESS_WRITE_SETTLE_SECONDS (60s, and never longer than the user's own alphaess_min_write_interval) of a successful write is treated as the rest of the same schedule update rather than a new one. The burst is capped at ALPHAESS_WRITE_BURST_MAX (3) writes, so the cost against the documented 24-hour write budget stays a small constant per pacing interval instead of becoming one write per tick.

Two properties are deliberately preserved:

  • Only successful writes open a burst. A rejected write applied nothing, so there is nothing half-applied to correct, and the existing 6053/6008 retry pacing is untouched.
  • The burst state is not persisted by save_control(). A restart falls back to full pacing (the safe direction) rather than handing a restart loop a fresh allowance each time.

Two existing tests moved their second write from t+10s to t+100s. Their intent — "a genuinely new change inside the interval is held, not dropped" — is unchanged; they just now sit outside the settle window instead of accidentally inside it.

Testing

  • python3 -m pre_commit run --all-files — all hooks pass (run_pre_commit's own invocation is blocked by this environment's permission mode, so its two halves were run separately).
  • Full quick suite (unit_test.py --quick, what run_pre_commit runs after the hooks) — all tests passed, 4 slow tests skipped, 104.59s.
  • tools/triage_test.sh alphaess_control — passes, including four new cases: the GH#4769 repro (asserting the chargeLimit values that actually reach the POST body are [10, 100], not just the call count), the burst cap and its return to normal pacing, a rejected write opening no burst, and the settle window never outlasting a shortened alphaess_min_write_interval.
  • The other five AlphaESS suites (alphaess_const, alphaess_api, alphaess_config, alphaess_publish, alphaess_storage) — all pass.

Notes

Two contributing factors were identified during triage; this PR fixes the second. The first — that adjust_charge_window() presses the commit button before adjust_battery_target() writes the target SoC (inverter.py:3226 vs inverter.py:2024) — is structural in execute.py, which writes every charge window before it reaches the set_soc_enable block, and affects Solis and Fox as well. Predbat's design already tolerates it by committing a second time with the corrected value; only AlphaESS's pacer turned that intermediate state into a five-minute one. Fixing the pacer reduces the exposure from ~300s to ~3s without touching the shared inverter write ordering. If you would rather the stale commit never went out at all, that is a larger change and worth doing separately.

Worth flagging from the same log: inverter_soc_reset: true would normally park charge_limit at 100 between windows and mask this entirely, but it is gated on not self.inverter_hybrid (execute.py:646, 682, 703, 715) and the reporter's system reports inverter_hybrid([True]), so it is inert for them.

🤖 Generated with Claude Code

…pacer

Predbat commits a schedule in stages - charge window, then the enable
switch, then the target SoC - pressing the schedule write button after
each one. The first commit of a cycle therefore carried whatever target
SoC the control entity still held from the previous cycle, and the
corrected value that arrived seconds later was held for the whole
alphaess_min_write_interval. On a manual charge for the live slot that
meant AlphaESS ran a "charge to 10%" schedule for five minutes with the
house on the grid.

A correction landing within ALPHAESS_WRITE_SETTLE_SECONDS of a
successful write is now treated as the rest of the same schedule update
and allowed through, capped at ALPHAESS_WRITE_BURST_MAX writes so the
24-hour write budget stays bounded. Only successful writes open a burst,
so rejected-write pacing (6053, 6008) is unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@springfall2008 springfall2008 self-assigned this Aug 27, 2026
@springfall2008
springfall2008 marked this pull request as ready for review August 27, 2026 17:44
Copilot AI lite review requested due to automatic review settings August 27, 2026 17:44

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The pacing change is narrowly scoped to AlphaESS, is explicitly bounded, and is backed by focused regression and behavioral tests covering the new exemption and its limits.

Pull request overview

This PR fixes an AlphaESS-specific pacing edge case where Predbat’s staged schedule commits (window → enable → target SoC) could leave the inverter running a stale target SoC for the full alphaess_min_write_interval, by allowing a small bounded “correction burst” shortly after a successful write.

Changes:

  • Add a settle-window exemption to the AlphaESS write pacer so same-cycle corrections can pass within a bounded burst after a successful write.
  • Introduce ALPHAESS_WRITE_SETTLE_SECONDS and ALPHAESS_WRITE_BURST_MAX constants and track per-(serial,direction) burst state in-memory (not persisted).
  • Update/extend AlphaESS control tests and adjust two existing tests to ensure they remain outside the settle window; update documentation to describe the correction behavior.
File summaries
File Description
docs/components.md Documents the new “same-cycle correction” behavior and why it exists for AlphaESS staged commits.
docs/apps-yaml.md Expands alphaess_min_write_interval documentation to explain the capped correction window after writes.
apps/predbat/tests/test_alphaess_control.py Adds targeted regression tests for GH#4769 and burst behavior; shifts two timing-based tests to avoid the settle window.
apps/predbat/alphaess.py Implements the correction-burst pacing exemption and burst accounting on successful writes only; keeps burst state non-persisted.
apps/predbat/alphaess_const.py Adds constants controlling settle window duration and burst cap.
Review details
  • Files reviewed: 5/5 changed files
  • Comments generated: 0
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@springfall2008
springfall2008 merged commit 596fb20 into main Aug 27, 2026
3 checks passed
@springfall2008
springfall2008 deleted the fix/alphaess-stale-charge-limit-4769 branch August 27, 2026 17:49
springfall2008 added a commit that referenced this pull request Aug 27, 2026
Read through the daemon's per-issue logs at ~/predbat-triage-bot/logs
(both the pending and reviewed/ directories) and added confirmed,
working-tree-verified findings the file didn't already have:

- create_debug_yaml() masks only the "args" key; args_from_apps_yaml
  (added v8.48.1) is a second, unmasked deepcopy with real secrets.yaml
  values, and is still present on main - a live credential-leak trap
  for anyone quoting a reporter's debug yaml in a comment.
- Solis: adjust_force_export()/adjust_inverter_mode() press the update
  button every cycle on H-M-format inverters regardless of whether
  anything changed, distinct from the existing CID 636 TOU-bit notes.
- AlphaESS: the one-write-behind schedule pacer bug (now fixed in
  PR #4776) and the legacy batUseCap floor (errno 10001 signature).
- Ohme's vendored ohmepy client was several versions behind upstream
  with withdrawn v1 control routes - also since fixed on main.
- New rows: Load ML CPU-spike mechanism, the savings_total vs
  savings_yesterday metric mismatch, and the components.py
  auto-config gate's false-positive "skipping interface" warning.
- Two new traps: SoC-quantisation false positives on "stuck charge"
  reports, and the Companion app ignoring Content-Disposition.

Each entry either cites the issue it came from or is marked as
already fixed on main, per the file's own "confirm before you write
it down" rule. Added the small handful of new dictionary words
(BLAS, threadpoolctl, unredacted, unreproduced) cspell flagged.
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.

ALPHAESS: Manual Charge didn't work.

2 participants