Rolling-horizon control with naive-mpc-optim#
Type: How-To Guide β task-oriented, follow when you need a live, continuously-updated optimization plan instead of a static day-ahead schedule.
The dayahead-optim action computes a single 24 h schedule once per day. For systems where forecasts and state change throughout the day (especially battery SOC, EV state, dynamic prices), you want Model Predictive Control: re-run the optimization on a rolling window every N minutes, using the latest measurements as the new initial state.
EMHASS implements this with the naive-mpc-optim action. This page walks through wiring it up.
Scenario#
Component |
Value |
|---|---|
PV |
5 kWp |
Battery |
5 kWh nominal, defaults: |
Deferrable loads |
as previous tutorials |
MPC re-run cadence |
every 30 minutes |
Prediction horizon |
24 h (number of timesteps = |
Configuration#
In addition to the parameters from Basic β PV + Battery, MPC needs runtime parameters per call. The following keys go into the request body, not the static config:
Runtime key |
Value |
|---|---|
|
number of timesteps to plan ahead |
|
current battery SOC, fraction of nominal capacity (read from your battery sensor) |
|
end-of-horizon SOC target (see note below) |
|
remaining required operating hours per deferrable load |
For the full list of runtime keys, see Passing data.
Note
soc_init and soc_final are read from runtimeparams independently. If one is omitted, EMHASS substitutes battery_target_state_of_charge (default 0.6) for that single value; it does not mirror the passed value onto the missing one. Always pass both explicitly. Two production-tested rolling-MPC patterns: soc_final = soc_init (current SOC for both, neutral trailing edge) or soc_final = 0 (with a 24 h horizon re-run every 30 min, the deadline is always 24 h away and never reached, so this behaves the same in practice). For a hard end-of-horizon target, pass that value and extend prediction_horizon so the deadline sits at the real point in time.
Run#
REST (recommended for HA rest_command automation):
rest_command:
emhass_mpc:
url: http://localhost:5000/action/naive-mpc-optim
method: POST
timeout: 120
headers:
content-type: application/json
payload: >
{
"prediction_horizon": 48,
"soc_init": {{ states('sensor.battery_soc') | float / 100 }},
"def_total_hours": [
{{ states('sensor.water_heater_remaining_hours') }},
{{ states('sensor.pool_pump_remaining_hours') }}
]
}
(soc_final is intentionally omitted; see the note above. Adjust the literal 48 if your optimization_time_step is not 30 minutes.)
Trigger it every 30 minutes:
automation:
- alias: EMHASS MPC every 30 min
trigger:
- platform: time_pattern
minutes: "/30"
action:
- service: rest_command.emhass_mpc
- service: shell_command.emhass_publish_data
For the matching shell_command.emhass_publish_data, see Automations.
Output#
After each MPC run, EMHASS publishes the same sensors as dayahead-optim (sensor.p_deferrable0, sensor.soc_optim, etc.), but the schedule covering the prediction horizon replaces the previous one. Your HA automations follow the current state of sensor.p_deferrable* to switch real loads on/off; they donβt care whether the underlying schedule came from dayahead-optim or naive-mpc-optim.
The plan-cycle behavior with a 30-minute re-run cadence and a 24 h horizon: at minute 0 the optimizer plans 24 h forward; at minute 30 it plans 24 h forward from the new βnowβ; at minute 60 again; and so on. Each plan partially overlaps the previous one for the next 23.5 h, but the new plan reflects the latest SOC, latest def_total_hours, latest forecast.
Interpretation#
MPCβs main advantage over day-ahead is resilience to forecast error: when actual PV generation diverges from the morning forecast, the next MPC iteration corrects course immediately.
MPCβs main cost is solver time Γ frequency. With CVXPY/HiGHS the solve is typically 0.1 β 0.5 s; running every 30 min is well within budget. Running every 1 min is overkill for most homes. See Good Practices.
For deferrable loads with strict windows (EV must charge by 07:00), pass
start_timesteps_of_each_deferrable_load/end_timesteps_of_each_deferrable_loadper load in the runtime payload. See EV walkthrough.
See also#
Tutorial: Basic β PV + Battery (the static day-ahead version)
Reference: Passing data for the full runtime params list
Reference: Automations for
shell_command/rest_commandpatternsHow-to: Heat-pump walkthrough
How-to: EV walkthrough
Explanation: Good Practices
Explanation: Advanced math model