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: battery_minimum_state_of_charge: 0.3, battery_maximum_state_of_charge: 0.9

Deferrable loads

as previous tutorials

MPC re-run cadence

every 30 minutes

Prediction horizon

24 h (number of timesteps = 24 Γ— 60 / optimization_time_step; with the default 30-minute step, that is 48)

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

prediction_horizon

number of timesteps to plan ahead

soc_init

current battery SOC, fraction of nominal capacity (read from your battery sensor)

soc_final

end-of-horizon SOC target (see note below)

def_total_hours

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_load per load in the runtime payload. See EV walkthrough.

See also#