EV charging as a deferrable load#

Type: How-To Guide β€” task-oriented.

Warning

Early-draft page. EV charging in EMHASS is an active community topic (discussion #789). The pattern below β€” treating the charger as a windowed deferrable load β€” covers the common case but has known limits. See Known limits at the end of this page before relying on it. Contributions and corrections welcome.

EMHASS treats an EV charger as a windowed deferrable load: a fixed energy amount that must be delivered before some end time, with charging power up to the charger’s rated nominal power. The optimizer then chooses when in the available window to charge.

This page covers the EMHASS-side configuration. The transport β€” how the EV’s required-energy and target-SOC reach EMHASS at runtime β€” depends on your charger and home-automation setup. Any source that can publish the right signals into Home Assistant (or directly into your rest_command payload) will work: OCPP-managed chargers, vehicle-API integrations (Tesla, Hyundai BlueLink, etc.), Modbus chargers, EVCC, or pure HA scripts (one community example).

Scenario#

Component

Value

EV charger

AC, single fixed charging power for the whole session

Vehicle

a target energy amount in kWh that must be delivered before a deadline

Existing system

PV + battery (per Basic β€” PV + Battery)

Optimization mode

naive-mpc-optim

Configuration#

The EV slot goes into nominal_power_of_deferrable_loads, with corresponding entries in operating_hours_of_each_deferrable_load, start_timesteps_of_each_deferrable_load, and end_timesteps_of_each_deferrable_load. Most of these are placeholders in the static config and get overridden per MPC call.

Example (assuming the EV is the third deferrable, after two existing loads):

nominal_power_of_deferrable_loads:
  - 3000        # water heater
  - 750         # pool pump
  - <CHARGER_W> # EV charger nominal power, e.g. 3700 (1ph 16A), 7400 (1ph 32A), 11000 (3ph 16A)
operating_hours_of_each_deferrable_load:
  - 5
  - 8
  - 0           # placeholder; overridden at runtime
start_timesteps_of_each_deferrable_load:
  - 0
  - 0
  - 0           # placeholder; overridden at runtime
end_timesteps_of_each_deferrable_load:
  - 48
  - 48
  - 0           # placeholder; overridden at runtime

Runtime payload#

Two values you compute fresh per MPC call:

Variable

Source

Calculation

def_total_hours[2]

a sensor that reports remaining kWh to deliver

kWh_remaining / charger_kW, rounded to whole hours (see Known limits)

end_timesteps_of_each_deferrable_load[2]

departure deadline

(deadline - now) / optimization_time_step_minutes, integer

A Home Assistant rest_command template, source-agnostic:

rest_command:
  emhass_mpc:
    url: http://localhost:5000/action/naive-mpc-optim
    method: POST
    timeout: 120
    headers:
      content-type: application/json
    payload: >
      {%- set charger_kw = 11.0 -%}
      {%- set timestep_min = 30 -%}
      {%- set horizon = 48 -%}
      {%- set ev_remaining_kwh = states('sensor.YOUR_EV_REMAINING_KWH') | float -%}
      {%- set ev_hours = (ev_remaining_kwh / charger_kw) | round(0, 'ceil') | int -%}
      {%- set deadline = today_at("07:00") if now() < today_at("07:00") else today_at("07:00") + timedelta(days=1) -%}
      {%- set departure_minutes = (deadline - now()).total_seconds() / 60 -%}
      {%- set end_step = (departure_minutes / timestep_min) | int -%}
      {
        "prediction_horizon": {{ horizon }},
        "soc_init": {{ states('sensor.battery_soc') | float / 100 }},
        "def_total_hours": [
          {{ states('sensor.water_heater_remaining_hours') }},
          {{ states('sensor.pool_pump_remaining_hours') }},
          {{ ev_hours }}
        ],
        "end_timesteps_of_each_deferrable_load": [{{ horizon }}, {{ horizon }}, {{ end_step }}]
      }

Replace sensor.YOUR_EV_REMAINING_KWH with the actual sensor your integration publishes. The contract is: a sensor that reports kWh remaining to deliver before the next deadline.

Output#

sensor.p_deferrable2 carries the optimized EV charging power per timestep. An HA automation drives the charger:

automation:
  - alias: EV charging follow EMHASS plan
    trigger:
      - platform: state
        entity_id: sensor.p_deferrable2
    action:
      - choose:
          - conditions:
              - condition: numeric_state
                entity_id: sensor.p_deferrable2
                above: 100
            sequence:
              - service: switch.turn_on
                target:
                  entity_id: switch.YOUR_EV_CHARGER
          - default:
              - service: switch.turn_off
                target:
                  entity_id: switch.YOUR_EV_CHARGER

Replace switch.YOUR_EV_CHARGER with the actual control entity for your charger.

Known limits#

Read these before designing around the pattern above.

  • Integer hours only. operating_hours_of_each_deferrable_load accepts whole hours, not fractions (issue #373). With a 30-minute timestep and an 11 kW charger, that means kWh-required is rounded up to the next 11 kWh increment. Reduce the rounding error by using a smaller optimization_time_step (e.g. 15 minutes) β€” but then 48 timesteps becomes 96.

  • Power modulation is not supported. EMHASS schedules the deferrable as either-on-at-nominal-power-or-off per timestep. Modulating chargers (e.g. Tesla 3.6–11 kW continuous) cannot be cleanly expressed as a single deferrable; you would need multiple parallel deferrable slots at different powers, or model only the upper bound.

  • Mid-session state is not forced. def_current_state flags the load as currently-on for startup-penalty purposes but does not force the optimizer to plan a non-zero power for the first timestep (issue #605). If you start charging manually mid-window, the optimizer may plan to stop and re-start.

  • No native vehicle-API integration. EMHASS does not query the vehicle SOC directly; you provide kWh-remaining via a sensor. The community has seen this gap and discussed approaches in #789, including a fork by @tomvanacker85 with calendar-driven SOC targets. None of those have been upstreamed.

  • Opportunistic / surplus-only charging is awkward. If you want β€œcharge only from PV surplus, no grid”, the deferrable-load model is not the right fit β€” the optimizer treats the kWh-target as mandatory. Surplus-only charging is typically handled outside EMHASS (in EVCC’s PV mode, or HA automations) with EMHASS optimizing the rest of the system around it.

Infeasibility#

If (end_step - start_step) Γ— charger_kw < required_kwh, the optimization is infeasible. EMHASS returns optim_status: "Infeasible" and publishes nothing. See Good Practices β€” infeasibility triage.

See also#