Background
Raised by @Frank-Leysen in #126 (comment 2026-07-11T13:36:32Z): on his Luminus Dynamic contract, sell/injection price goes negative on sunny midday hours. His HA scripts block grid export when the injection price is <= 0, but BESS's optimizer has no equivalent — when solar surplus exceeds house load plus available battery charge room, the excess is unconditionally exported at whatever the sell price is, including negative.
Root cause (confirmed in code)
core/bess/dp_battery_algorithm.py — _state_transition / _compute_reward (STORE and IDLE dispositions, ~lines 192-223 and 294-314): surplus_exported = max(0.0, surplus - solar_to_battery) is computed unconditionally once solar exceeds min(max_charge_power_kw, remaining battery room). The reward term already penalizes this correctly when sell_price < 0 (-grid_exported * current_sell_price becomes a cost), so the DP "wants" to avoid it — but there is no action available to actually avoid it. There is no PV curtailment action anywhere in the DP action space, and no export-limit integration in growatt_sph_controller.py / growatt_min_controller.py.
Hardware capability (confirmed, not yet integrated)
Growatt hybrid inverters (SPH / MOD-XH series, includes Frank's MOD 5000TL3-XH GEN4) support a genuine export-limit / curtailment function via Modbus:
- Register 201: export limit value (or percentage on some models)
- Register 202: enable export-limit control
This uses a CT/smart-meter at the grid connection point (e.g. SPM-CT-E) to measure actual export and throttle PV/MPPT production down — it curtails real panel output, not just a grid-meter cap that gets bypassed once the battery is full. One forum report noted a small residual overshoot (~20-50W) even at a 0 W setting, so it isn't perfectly airtight, but it does what's needed here.
Sources:
Open question for Frank: does he have the CT/smart-meter hardware connected/available? This feature is only viable if that's present.
Proposed feature
When forecast sell price for a period is negative (or below a user-configurable floor), and the DP has no better use for the surplus (battery full / no headroom), write the Growatt export-limit register (0 or a configured cap) for that period instead of allowing full-rate export; clear/restore the limit once price is non-negative again. No change to the DP objective is needed — the reward function already penalizes negative-price export correctly; this is purely a new actuation path.
Scope
- New: export-limit register read/write in the Growatt GEN4 Modbus controller
- New: per-period decision of when to apply the limit (likely a straightforward "sell_price < 0" check against the existing schedule, not a DP action-space change)
- Confirm hardware prerequisite (CT/smart-meter) with affected users before committing to implementation
Not a regression — no current version of BESS ever had PV curtailment.
Background
Raised by @Frank-Leysen in #126 (comment 2026-07-11T13:36:32Z): on his Luminus Dynamic contract, sell/injection price goes negative on sunny midday hours. His HA scripts block grid export when the injection price is
<= 0, but BESS's optimizer has no equivalent — when solar surplus exceeds house load plus available battery charge room, the excess is unconditionally exported at whatever the sell price is, including negative.Root cause (confirmed in code)
core/bess/dp_battery_algorithm.py—_state_transition/_compute_reward(STORE and IDLE dispositions, ~lines 192-223 and 294-314):surplus_exported = max(0.0, surplus - solar_to_battery)is computed unconditionally once solar exceedsmin(max_charge_power_kw, remaining battery room). The reward term already penalizes this correctly whensell_price < 0(-grid_exported * current_sell_pricebecomes a cost), so the DP "wants" to avoid it — but there is no action available to actually avoid it. There is no PV curtailment action anywhere in the DP action space, and no export-limit integration ingrowatt_sph_controller.py/growatt_min_controller.py.Hardware capability (confirmed, not yet integrated)
Growatt hybrid inverters (SPH / MOD-XH series, includes Frank's MOD 5000TL3-XH GEN4) support a genuine export-limit / curtailment function via Modbus:
This uses a CT/smart-meter at the grid connection point (e.g. SPM-CT-E) to measure actual export and throttle PV/MPPT production down — it curtails real panel output, not just a grid-meter cap that gets bypassed once the battery is full. One forum report noted a small residual overshoot (~20-50W) even at a 0 W setting, so it isn't perfectly airtight, but it does what's needed here.
Sources:
Open question for Frank: does he have the CT/smart-meter hardware connected/available? This feature is only viable if that's present.
Proposed feature
When forecast sell price for a period is negative (or below a user-configurable floor), and the DP has no better use for the surplus (battery full / no headroom), write the Growatt export-limit register (0 or a configured cap) for that period instead of allowing full-rate export; clear/restore the limit once price is non-negative again. No change to the DP objective is needed — the reward function already penalizes negative-price export correctly; this is purely a new actuation path.
Scope
Not a regression — no current version of BESS ever had PV curtailment.