Owner decision 2026-09-29. Selling is off until it can be judged on the price
the grid pays: packmode.SELLING_ENABLED = False makes read() answer
sell=False whatever the store holds, pack_sell is refused ("Selling is off
for now."), and the page has no selling switch; the sell code stays. Until
the P4 forecast, only Cheap hours only charges from the grid
(packmode.GRID_CHARGE_MODES); Leftover solar holds and gives in the
expensive runs. Help, Today and plan lines, DOCS and DECISIONS follow.
Replay, 16 days: Solar first + Leftover solar 19.44 EUR net (P2 19.33).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
78 lines
5.2 KiB
Markdown
78 lines
5.2 KiB
Markdown
# 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 to
|
|
a5dddad (`test_modes.py` part 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.py` part 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 what ed739d4 did (`test_mqtt.py` part 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 to 7a00f12 (`test_modes_p3.py` part 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 hourly prices a
|
|
year earlier, modelled batteries), net EUR, lower is better: P2 19.33, P3
|
|
Solar first + Leftover solar 19.57, with a sale 19.60, the old one-period
|
|
trade 18.14. Review of #153 (same model, with a per-action split):
|
|
- the cheapest hours were at midday on 15 of 16 days, so Leftover solar (acting
|
|
as Cheap hours until P4) bought 12.2 kWh there that the sun would have
|
|
given: 7 kWh more exported, 12 kWh less solar stored. Holding for the
|
|
expensive hours saved 4 kWh of import, not enough. With no grid charge on
|
|
Leftover solar: 19.44;
|
|
- the old trade charged one hour, so the afternoon sun still fit;
|
|
- a sale earned the wholesale price (2.27 EUR for 11.4 kWh) and the house
|
|
then bought the same energy back (2.29 EUR): a wash before wear. It pays
|
|
only when the sale hour's wholesale price beats the later import price,
|
|
levies included (about 0.13 EUR/kWh above that hour's wholesale here);
|
|
- valuing the charge left at the end, wear and real Flemish levies make
|
|
selling worse and do not change the order.
|
|
- Owner decision 2026-09-29 (option A): selling is off until it can be judged
|
|
on the price the grid pays (`packmode.SELLING_ENABLED`, code kept), and
|
|
Leftover solar takes solar only until P4's forecast
|
|
(`packmode.GRID_CHARGE_MODES`); Cheap hours only still charges from the grid. Replayed
|
|
(16 days, as above): Solar first + Leftover solar 19.44 EUR net against
|
|
P2's 19.33; Solar first + Cheap hours only 19.57.
|
|
- Not done in P3: the forecast (P4), and a headroom too short for the run
|
|
moving the rest to the next cheap periods.
|