Optimizer
Which solver decides what your battery does next, how finely it plans, and the limits it has to stay inside.
What this is for
- I want the battery to charge when power is cheap and carry me through the expensive hours.
- I want to choose between the solver built into EOS Connect and one I already run myself.
- I want to keep the plan inside the import and export limits my grid operator gave me.
Everything on this page sits under eos.*. What the optimizer is
given — prices, forecasts, measured load — is configured on the
other pages in this section.
Choosing an optimizer
The optimizer decides what your battery does next. Set it in
eos.source.
local_evopt
The default. A mixed-integer solver running inside EOS Connect. Nothing else to install or keep alive.
eos_server
Akkudoktor EOS, running separately. The most detailed house model, and the only backend that uses the outside-temperature forecast.
evopt
An external EVopt server. Lightweight, fast, and useful if you already run one.
Tuning the built-in optimizer
Three settings shape its behaviour, and all have sensible defaults:
eos.local_evopt_charging_strategy— defaultcharge_before_export: fill the battery before selling surplus to the grid. Alternatives aredischarge_before_import,maximize_self_consumption,attenuate_grid_peaksandnone.eos.local_evopt_discharging_strategy— defaultdischarge_before_import: use stored energy before buying. Oremergency_reserve, ornone.
eos.local_evopt_terminal_soc_value — default
cheapest_ahead: decides what charge still in the battery when the
planning horizon runs out is worth. This matters more than it sounds. The
optimizer plans a fixed window and then stops, so whatever it thinks the
leftover charge is worth is what decides whether it saves energy for tomorrow
or spends it tonight.
cheapest_aheadvalues it at the lowest electricity price in the horizon. Charging and discharging both lose a little to conversion, so this leaves a band around that price in which the battery simply holds: nothing is cheap enough to be worth buying just to store, and nothing near the bottom of the tariff is dear enough to be worth emptying into. It fits the way the planning window is cut — it always ends at midnight, and midnight is always followed by the cheapest hours of the night, so charge left over at the end can be bought back shortly afterwards for about the same price. On a day with cheap grid power available throughout, that means the battery is expected to end close to empty, and that is the correct answer rather than a fault.stored_priceis the old behaviour, kept so you can go back: leftover charge is valued at what it cost to put there. On a mostly solar-charged battery that is close to nothing, so the optimizer treats a full battery at the end of the horizon as near-worthless and empties it — even into cheap hours, and even when it means exporting solar surplus for the feed-in tariff rather than keeping it.
The external EVopt server runs the same engine and is sent the same figure,
so eos.external_evopt_terminal_soc_value does the same job when
eos.source is evopt. Only the slots genuinely ahead
are considered — in quarter-hourly mode the forecast wraps around, and
yesterday's prices trailing the end of it are not offers you can still take.
Set eos.local_evopt_emergency_reserve_pct above 0 to hold a hard
floor of charge in reserve at the end of the planning horizon, for outage
cover. It is a floor, not an economic signal — deciding how much charge
is worth keeping is
eos.local_evopt_terminal_soc_value’s job.
If your grid connection is limited, cap it explicitly with
eos.local_evopt_max_grid_import_w and
eos.local_evopt_max_grid_export_w. Both default to
0, meaning "use the inverter's own limit".
eos.local_evopt_num_threads and
eos.local_evopt_time_limit bound the CBC solver on slow hardware;
0 means no limit.
The external-EVopt equivalents,
eos.external_evopt_max_grid_import_w and
eos.external_evopt_max_grid_export_w, ship with a default of
10000 W, not 0. The fallback to the
inverter limit therefore never fires unless you set them to
0 yourself.
Planning resolution
eos.time_frame sets how finely the day is sliced:
3600 seconds (hourly, the default) or 900 (quarter-hourly).
Quarter-hourly follows a volatile tariff more closely and costs more computation.
Changing it needs a restart.
Dynamic PV override
Set eos.dyn_override_discharge_allowed_pv_greater_load to allow
discharging in any slot where forecast solar exceeds forecast load, even if the
optimizer planned to hold. It stops the battery sitting idle through a bright but
intermittently cloudy afternoon that the plan treated as one flat block. Off by
default.
It never overrides a slot where grid charging was requested, and a manual override you set in the dashboard always wins over it.
The rest of the optimizer settings
Timeouts, solver limits, and the import and export ceilings your grid operator sets — the last of these are explained under Grid import and export limits.