Skip to content

Dev to Main#93

Merged
bvweerd merged 20 commits into
mainfrom
dev
Jul 12, 2026
Merged

Dev to Main#93
bvweerd merged 20 commits into
mainfrom
dev

Conversation

@bvweerd

@bvweerd bvweerd commented Jul 12, 2026

Copy link
Copy Markdown
Owner

No description provided.

bvweerd and others added 20 commits July 10, 2026 13:55
Users confused "Eff. kW" (effective power from the DP schedule after
hybrid/commitment resolution) with an efficiency metric, and were
puzzled to see it pinned at 0 while the battery was visibly charging.
This is expected: in zero_grid mode there's no fixed schedule power,
it's recalculated continuously by the real-time zero-grid controller
instead, and shown in "Setpoint kW" / the Real-time Setpoint Log.
Add a note and a column tooltip explaining this.
effective_power_kw is always 0 during zero_grid mode by design (the
real-time controller determines power dynamically, not the DP
schedule), so comparing it to setpoint_kw unconditionally made the
"SoC/power limited" warning tip and red row-highlight fire on nearly
every normal zero_grid charge/discharge cycle -- a false positive
unrelated to any actual SoC or power constraint. Skip the comparison
when effective_mode is zero_grid, in both the tip generator
(analyzer.js) and the run-log table highlighting (index.html).

The setpoint_log's own soc_limited flag (computed in
coordinator_optimization.py by comparing dp_schedule_w vs
raw_target_w) is unaffected: it already requires dp_schedule_w > 50W,
which is never true during zero_grid (controller_schedule_w is 0
there too), so it doesn't share this false-positive.
…grid

fix: clarify Eff. kW and stop false "SoC/power limited" warnings during zero_grid
…rice forecast

Hybrid mode charges the battery as soon as PV surplus appears, even when
the forecast shows abundant cheaper surplus later in the day (e.g. the
midday PV peak at low prices). The new hybrid+ mode behaves like hybrid,
but only stores surplus when the DP shadow price says the stored energy
is worth more than exporting it: capture requires lambda x sqrt(RTE) >=
current feed-in price, with a +/-5% hysteresis band to prevent
oscillation. When capture is blocked the surplus is exported at the
current feed-in price and the battery charges later from the cheaper
surplus via the regular DP schedule.

The realtime idle->zero_grid upgrade respects the same block so surplus
appearing between optimizer runs is not captured anyway. Discharge
decisions, self-consumption on grid import, and negative feed-in
handling (always capture) are unchanged from hybrid.
Community forum questions (community.home-assistant.io/t/re-custom-integration-battery-controller/1016849) showed users were confused why Hybrid mode charges the battery via zero-grid even at a positive feed-in price, and why the schedule can look different between two closely-timed screenshots.
…run deviations

The README and ALGORITHM.md described the shadow price as feeding forward into
the next run's terminal condition. optimizer.py no longer does this (deliberately,
to avoid a circular self-suppression bug at peak prices) — the terminal value
comes from a tail-average of the feed-in forecast every run. Replaced with an
accurate explanation of why the DP's full-horizon re-solve (a global allocation
of limited capacity across price opportunities) can make two runs a few minutes
apart look meaningfully different, which is what triggered the community
forum question this addresses.
Same fix as the README/ALGORITHM.md correction: the DP terminal condition
comes from the feed-in forecast tail, not the previous run's shadow price.
Fixed the misleading comment in diagnostics.py, the print text in
simulate_diagnostics.py, and a stale label plus dead empty if-block in
docs/index.html. No behavior change — text/comments only.
…osdoip

docs: clarify Hybrid mode trade-off and rolling-horizon re-optimization
Two independent bugs were causing the erratic behavior:

- The feed-in price sensor was never tracked for state changes when it's
  a separate entity from the buy price sensor, so current_feed_in_price
  (and PVCurtailmentSensor) only refreshed on buy-price events, the
  mid-period timer, or the 60-min base update_interval — lagging the
  actual feed-in price by up to an hour.
- Condition B (battery absorption limiting) compared live, noisy
  battery power against a single threshold on every real-time
  zero_grid update (every ~5-10s), flapping the sensor independent of
  any price change. Added an entry/recovery hysteresis band, matching
  the pattern already used for hybrid zero_grid/charging transitions.
…-bxvwup

fix: PV Curtailment Suggested lagging feed-in price and flapping mid-period
…-jzltzl

feat: add hybrid+ control mode that gates PV-surplus capture on the price forecast
When the user switches control mode (e.g. hybrid → follow_schedule), the
real-time loop previously continued using the cached _effective_mode from
the previous mode (e.g. "zero_grid") until the re-optimization triggered by
async_request_refresh() completed. This caused a brief window where the old
mode's setpoint was still applied, and made the transition non-atomic.

Changes:
- control_mode setter now resets _effective_mode = ACTION_IDLE and
  _controller_schedule_w = 0.0 so the real-time loop immediately applies
  a safe idle setpoint while the re-optimization is running.
- Added _optimization_trigger_source tracking: each trigger site now labels
  the run (mode_change, price_boundary, price_spike, price_available,
  mid_period, feed_in_price_change, soc_available, forecast_available,
  pending_rerun). The label appears in the optimizer_run_log for diagnostics.
- Extended test_mode_switch_resets_commitment to cover the new resets.

Closes #88

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When the user switches control mode (e.g. hybrid → follow_schedule), the
real-time loop previously continued using the cached _effective_mode from
the previous mode (e.g. "zero_grid") until the re-optimization triggered by
async_request_refresh() completed. This caused a brief window where the old
mode's setpoint was still applied, and made the transition non-atomic.

Changes:
- control_mode setter now resets _effective_mode = ACTION_IDLE and
  _controller_schedule_w = 0.0 so the real-time loop immediately applies
  a safe idle setpoint while the re-optimization is running.
- Added _optimization_trigger_source tracking: each trigger site now labels
  the run (mode_change, price_boundary, price_spike, price_available,
  mid_period, feed_in_price_change, soc_available, forecast_available,
  pending_rerun). The label appears in the optimizer_run_log for diagnostics.
- Extended test_mode_switch_resets_commitment to cover the new resets.

Closes #88

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…cking

fix: #88 immediate setpoint reset on mode change + trigger source tracking
… control mode

The control_mode setter resets the commitment filter and (since #88) also
forces the cached setpoint to idle so the real-time loop stops applying the
previous mode's schedule while the mode-change re-optimization runs.

Both resets also fired when the selected mode was already active: HA's
select entity calls async_select_option on every service call, so an
automation (or voice assistant) that periodically re-asserts the current
mode would wipe the commitment filter and drop the battery to an idle
setpoint until the next optimization completed — churn for a no-op.

Guard the setter: when the requested mode equals the current mode there is
no stale state to clear, so return without touching anything.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XJ1durpbByC5PtwhFkiHzm
The sensor was intended to signal the price condition only: feed-in price
negative = curtailment suggested. The battery-absorption conditions (SoC near
maximum, actual charge power below the setpoint) second-guessed that intent
and made the sensor state depend on live battery power, which also required
hysteresis to keep it from flapping between DP re-optimizations.

is_on is now simply current_feed_in_price < 0. The absorption conditions,
the condition-B hysteresis latch, and the now-unused ABSORPTION_THRESHOLD
constant are removed. The battery/setpoint diagnostic attributes on the
sensor are kept unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XJ1durpbByC5PtwhFkiHzm
…-mode-reselect

fix: don't reset commitment and setpoint when re-selecting the active control mode
fix: make PV Curtailment Suggested purely price-based
@github-actions github-actions Bot added documentation Improvement or addition to documentation enhancement New feature or improvement labels Jul 12, 2026
@bvweerd
bvweerd merged commit f85004a into main Jul 12, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvement or addition to documentation enhancement New feature or improvement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants