Cheap and expensive hours as runs of consecutive firm price slots
(priceruns.py); each battery's role per tick from its mode (modetrade.py):
grid charging for Leftover solar / Cheap hours only, never Solar first;
holding for the expensive runs; selling per battery (pack_sell) above the
spread and the floor. The site trade is sized inside the modes (loop bound)
and the peak guard's headroom; a run edge reseeds the loop. Cheap hours only
and selling are enabled on the Batteries screen and in the HA select
(appended). The site Why row no longer says "held back" when the peak limit
pushes power up (F2). Modes off: byte-identical to 7a00f12 on the trade path.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
4.1 KiB
4.1 KiB
OPT-11 - battery modes, per battery
A battery's mode (on/off, Solar first, Leftover solar) is a soft unit limit at
P_MODE = 35 that always holds 0 W. A SET above 35 (a safety stop) overrides
it; a limit above 35 (the peak guard) makes it give way only as far as that
limit needs, and a battery its mode holds back then carries only what the
others cannot (arbiter._unit_modes(), _site_mode()). Design and owner answers: goodwe-session/OPT-11-design.md (2026-09-27);
P1 is its section 6 P1, and the pull request lists what was measured.
- Off by default (
battery_modes_enabled); off, commands are byte-identical toa5dddad(test_modes.pypart 1). - Off is a mode range, so the peak guard can still use an Off battery when the others cannot cover the floor (design sections 2-3). Whether Off should be absolute is an open owner question.
- Review (PR #149): releasing only when the peak floor bound S let a trade won
above the floor keep the modes and place less than the floor, and let a
capped grid charge release an Off battery. Both are graded in
test_arbiter.pypart 11. The loop bound leaves a side that reaches the rail to the rail, so SAFETY-03 still flags a loop winding into it. - P2 (HA entities): a select and a switch per battery, only while modes are
on; commands go through
handle_action(), the page's path. A retained command is dropped, not applied: the store already holds the last choice, and a replay after a restart could otherwise undo a newer one. The config topics announced are kept in the store (mqtt_mode_configs), so turning modes off removes exactly those, and an install that never turned them on publishes whated739d4did (test_mqtt.pypart 10). A removed topic stays in that list and its empty config is re-sent on each start, so a start with no broker cannot lose it; re-sending is harmless. The HA options are the page's English names, pinned by a test, and are not renamed with the page: automations send them. - In P1 only Off closes a direction; Leftover solar differs by charging last.
- P3 (price runs,
priceruns.py,modetrade.py): each battery's range is narrowed by its role this tick (hold, give, charge, rest, sell); main.py files the intersection with the switch, so it still holds 0 W. With modes on the site trade is filed by the modes, sized from the loop bound (so no battery a mode or a refusal holds is asked for watts, P1's unmet share) and the guard's headroom;arbitrage.decide()runs only with modes off, which stays byte-identical to7a00f12(test_modes_p3.pypart 1). - SAFETY-46 item 2 is decided as the owner answered (2026-09-25): a trade is a run of consecutive firm slots. It applies with battery modes on; the modes-off trade keeps its one-period shape, because that path must not move.
- Choices that were measured, not assumed (tools/opt11p3_replay.py):
- a sale needs the guard to have measured the quarter, not to be clear: a 5 kW discharge alone reads "engaged" on export and flipped the sale every 5 s;
- a grid charge makes the others rest, so it starts only at 500 W and, once the guard or the headroom stops it, stays off for that 15-minute period (without: 13 flips in 4 minutes under a 3.5 kW house);
- stored energy is valued at the last cheap run for 12 h, and a charge needs its own run to clear the spread (without: a night charge was spent on flat morning prices, then charging went on at them);
- a run edge reseeds the loop at the house's level (a 3 kW import spike removed; about 0.1 x rating still overshoots as export).
- Replay (tools/opt11p3_replay.py, 16 recorded days, EPEX prices a year earlier, modelled batteries): P3 does not beat the P2 baseline on money (net 19.57 / 19.60 EUR against 19.33; the old one-period trade 18.14). A sale is judged on import prices but exports earn the injection price, so selling lost money in the model. The owner should see this before selling is recommended.
- Not done in P3: the forecast (P4: Leftover solar acts as Cheap hours only), a headroom too short for the run moving the rest to the next cheap periods, and an HA switch for selling.