If you're charging your EV the moment you plug it in, you're likely overpaying by 40-50%. Smart charging with Home Assistant can automatically optimize when and how your car charges, saving hundreds of euros per year while extending battery life.
In this guide, we'll cover everything from basic price-optimized charging to advanced solar integration and the emerging vehicle-to-home (V2H) technology.
Why Smart EV Charging Matters
The math is simple. Let's say you drive 300 km per week with an EV consuming 0.2 kWh/km:
- Weekly consumption: 60 kWh
- Annual consumption: 3,120 kWh
- Peak rate charging (€0.35/kWh): €1,092/year
- Off-peak charging (€0.18/kWh): €562/year
- Annual savings: €530
And that's before considering solar integration, which can reduce costs to near-zero.
None of this works without measurement first. Price-based and solar-based charging both depend on live power and energy sensors, so if you haven't got those yet, start with our Home Assistant energy monitoring guide and come back once your grid and solar sensors are reporting.
Compatible Home Chargers
The good news: most modern smart chargers work with Home Assistant. What matters is not just whether an integration exists, but whether it gives you an entity you can write an amp value to. Without that, you can only switch charging on and off — which rules out solar-surplus tracking and load balancing.
| Charger | How it installs | Local or cloud | What you get for current control | Caveat |
|---|---|---|---|---|
| Easee Home / Charge | HACS custom integration | Cloud API | number.*_dynamic_charger_limit plus the easee.set_charger_dynamic_limit service; switch.*_is_enabled to pause |
Easee's own circuit-level load balancing will silently override you. Set the circuit limit to your real fuse rating and drive only the dynamic limit from Home Assistant. |
| myenergi Zappi | HACS (myenergi) | Cloud API | select.*_charge_mode (Fast / Eco / Eco+ / Stopped) and number.*_minimum_green_level |
No free amp control. The Zappi does its own solar diversion from its own CT clamps, so you switch modes rather than modulate current — good if you want it hands-off, frustrating if you want your inverter's numbers to drive the decision. |
| Wallbox Pulsar / Copper | Core integration | Cloud (myWallbox account) | number.*_max_charging_current and switch.*_pause_resume |
Cloud polling, so a surplus automation reacts in tens of seconds, not instantly. Wallbox has changed its API before and broken the integration; keep a manual schedule as a fallback. |
| go-e Charger | Core integration | Local HTTP | number.*_max_current and switch.*_allow_charging |
You must enable the local HTTP API (v2) in the go-e app first. Older firmware speaking only API v1 needs the HACS version instead. |
| Tesla Wall Connector (Gen 3) | Core integration | Local | Nothing — it is sensors only (power, session energy, handle temperature) | Amp control has to come from the car, via a Tesla account integration exposing number.*_charging_amps. Plan for the car, not the charger, to be your control point. |
| KEBA KeContact P30 | Core integration | Local UDP | keba.set_current service, plus start/stop services |
Only the c- and x-series speak UDP, and you have to enable it in the charger's own configuration first. The base model cannot be controlled at all. |
| Ohme Home Pro | Core integration | Cloud API | switch.*_max_charge and number.*_target_percentage |
Ohme is schedule-first by design. You get target SoC and a boost, not continuous current control, so solar tracking is coarse. |
| openWB | MQTT | Local | Publish to the charge-point current topic, e.g. openWB/set/chargepoint/1/set/current |
Topic layouts differ between openWB 1.9 and 2.x. Confirm yours with an MQTT explorer before writing automations against it. |
Entity names are shown with a wildcard because each integration prefixes them with your charger's name — confirm yours in Developer Tools → States before pasting any YAML.
The car's state of charge is a separate integration again. Hyundai Ioniq 5 and Kia owners use the HACS hyundai_kia_connect integration; Volvo EX30 drivers get SoC from the Volvo Cars integration in recent releases. Poll these gently: the manufacturer APIs are rate-limited, and aggressive polling wakes the car and drains its 12 V battery. Prefer the charger's own kWh sensor and treat car SoC as a sanity check.
Where Your Price Data Comes From (Europe)
Every automation below reads a price sensor, so this is the piece to get right first. In practice there are four sources worth knowing:
- Nordpool (HACS) — day-ahead prices for the Nordics, the Baltics and much of the continent. Its
raw_todayandraw_tomorrowattributes hand you a list of hourly start times and values, which is exactly what you need to pick cheap slots yourself. Configure VAT and your supplier's markup so the sensor reads in real €/kWh, not bare spot. - ENTSO-e (HACS) — free API key, covers every bidding zone including NL, BE, DE-LU, DK1/DK2, SE1–SE4 and FI. Gives you
sensor.current_electricity_market_priceplus anaverage_electricity_price_todaysensor whosepricesattribute holds the full curve. Use it where Nordpool does not reach. - Tibber (core integration) — least work if Tibber is your supplier: the sensor is your actual all-in price including grid fees and tax, and it also exposes a cheap / normal / expensive
level. - Energi Data Service (HACS, Denmark) — DK1/DK2 spot plus the Danish hourly grid tariff, which matters more than spot. Radius and the other DSOs charge far more between 17:00 and 20:00, so a naive "cheapest spot hour" automation can cost you money. Eloverblik is the companion source for settlement history.
Two gotchas: tomorrow's curve publishes in the early afternoon CET, so a nightly planner must run after that rather than at midnight; and several EU markets are moving to quarter-hourly settlement, so read whatever slot length the sensor returns instead of hard-coding 24 values.
You also need consumption data to know what you saved — on Dutch and Belgian meters that means the P1 port, via DSMR or a HomeWizard dongle. The energy monitoring guide covers wiring that up, and our breakdown of European electricity prices explains why the cheap-to-expensive spread differs so much between NL, DE and DK.
The EV Smart Charging Integration
The EV Smart Charging custom integration for Home Assistant is a game-changer. It automatically calculates the cheapest charging windows based on your electricity prices.
Here's how it works:
- You set your departure time and desired charge level
- The integration fetches electricity prices (Nordpool, Tibber, Essent, etc.)
- It calculates which 15-minute intervals give the lowest total cost
- Charging starts and stops automatically during those windows
Pro Tip
Set your "ready by" time slightly earlier than when you actually need the car. This gives the algorithm more flexibility to find cheap slots, potentially saving even more.
Home Assistant vs the Alternatives: evcc, ChargeHQ, Tibber and Vendor Apps
Home Assistant is not the only way to do this, and not always the best way. Before you spend an evening on YAML, here are the five approaches European EV owners actually use.
| Approach | Setup effort | Cost | Solar surplus | Dynamic tariffs | Car state of charge |
|---|---|---|---|---|---|
| Home Assistant (own automations) | High — an evening to a weekend, and you maintain it | Free; you already run the server | Yes, exactly as you define it — any inverter, any threshold | Yes — Nordpool, ENTSO-e, Tibber, Energi Data Service | Yes, via a separate car integration |
| evcc | Medium — one YAML config file, an hour or two | Free and open source; a sponsor token unlocks some vehicles and chargers | Excellent — built in, including phase switching and minimum-SoC guarantees | Yes — Tibber, aWATTar, ENTSO-e built in | Yes — broad built-in vehicle support, no extra integration |
| ChargeHQ | Low — connect accounts in an app | Free tier; subscription for the full feature set | Yes, via your inverter's cloud API | Partial in the EU — strong on time-of-use, thinner on hourly spot | Yes, via the car maker's cloud |
| Tibber app | None — it is already on your phone | Free, but only if Tibber is your supplier | No — it cannot see your inverter | Yes — it is your tariff | Yes, for supported cars |
| Charger vendor app (Easee, Zappi, Wallbox) | None — it shipped with the box | Free with the hardware | Zappi yes (own CT clamps); Easee and Wallbox effectively no | Varies by market; usually schedules, not spot optimisation | No |
Where each one genuinely wins
- evcc is the best pure charging controller of the five, and it is not close. If the car is the only problem you are solving, evcc will beat automations you write yourself — phase switching, per-vehicle profiles and a charge plan Home Assistant has no equivalent of. It also runs as a Home Assistant OS add-on and reports back over MQTT, so "evcc or Home Assistant" is a false choice.
- Home Assistant's own automations win when charging is one consumer among several. If your heat pump, hot water cylinder, home battery and car all compete for the same cheap hours and the same surplus, something has to arbitrate, and that something is your automation engine. It is also the only option here that keeps the data local and feeds the Energy dashboard's long-term statistics.
- ChargeHQ wins on time-to-value: ten minutes of linking accounts, no server, no YAML, no maintenance.
- The Tibber app wins if you are already a Tibber customer without solar. Free, uses your real contract price, captures most of the available saving.
- The vendor app wins when you want a warranty and one throat to choke. A Zappi in Eco+ does solar diversion perfectly well without any of this.
When Home Assistant is the wrong answer
If you want a turnkey box, never intend to open a YAML file, and do not want a server you are responsible for patching, do not build this. Buy an Ohme or a Zappi, or run evcc from a template config. An automation that breaks silently at 02:00 and leaves you an empty car is worse than a dumb schedule that always works.
Solar-Integrated Charging
If you have solar panels, the real magic happens when you combine price optimization with solar excess charging. The logic:
- Priority 1: Charge from solar excess (free energy)
- Priority 2: Charge during cheapest grid periods
- Priority 3: Emergency charge if battery too low for tomorrow's trip
A typical automation monitors your solar production vs. house consumption. When production exceeds consumption by more than 1.4 kW (minimum charging current for most EVs), charging begins automatically.
alias: "Solar Excess EV Charging"
trigger:
- platform: numeric_state
entity_id: sensor.solar_excess_power
above: 1400
for: "00:05:00"
action:
- service: switch.turn_on
target:
entity_id: switch.ev_charger
Our Energy Dashboard Guide provides normalized price and energy data, cost dashboards, and reusable patterns that can support a larger energy setup. It does not include vendor-specific EV charger control; check what is inside before buying.
Vehicle-to-Home (V2H): The Future is Here
In October 2025, the first mass-market vehicle-to-home demonstrations launched in California, and this technology is coming to Europe fast.
The concept: your EV battery becomes a home battery. During grid outages or expensive peak periods, power flows from your car back into your home.
Current requirements:
- A V2H-capable vehicle (Kia EV6, Hyundai Ioniq 5/6, certain Nissan models)
- A bidirectional charger (Wallbox Quasar, Dcbel r16)
- Home Assistant integration for intelligent control
While still early, V2H can provide 10-20 kWh of backup power - enough to run essential appliances for 1-2 days during an outage.
Load Balancing
If you have multiple high-power devices (heat pump, induction cooktop, dryer), you need load balancing to avoid tripping breakers or exceeding your grid connection limit.
Home Assistant can monitor total house consumption and dynamically adjust EV charging current:
- House using 8 kW of your 10 kW connection? EV charges at 2 kW
- Heat pump cycles off? EV ramps up to full power
- Someone turns on the oven? EV reduces charging current
This prevents expensive grid upgrades while maximizing charging speed when capacity is available.
Get the reference point right: it is a per-phase limit, not a total. Dutch and Belgian homes are typically 3×25 A, older ones 3×16 A. A 3×16 A charger pulling 11 kW leaves just 9 A per phase for the house on a 3×25 A supply, and an induction hob plus a kettle will clear that. So you throttle against the worst-loaded single phase — hence the max() of three P1 current readings in the automation below, not a sum. Dutch capacity-based tariffs are the same problem seen from the billing side; our peak shaving guide covers that.
Four Automations That Cover Almost Every Setup
These four are what a complete setup actually consists of. They are written against an Easee charger, a Hyundai Ioniq 5 and a HomeWizard P1 meter, but the shape does not change — substitute your own entity ids.
All four share three helpers, created under Settings → Devices & services → Helpers:
input_datetime.ev_departure— time only, no datenumber.ev_target_socor aninput_number— the SoC you want to wake up to, typically 80input_number.ev_max_amps— the ceiling your charger and cable can take, e.g. 16
1. Departure-time charging
This works backwards from your charge rate: it stays idle until the time left before departure equals the time still needed, so the car finishes just in time rather than sitting at 80% for six hours. It assumes your car reports state of charge; if it does not, replace the SoC sensor with a fixed number of hours.
First, a template sensor that answers "how long do we still need?":
template:
- sensor:
- name: "EV Hours To Target"
unique_id: ev_hours_to_target
unit_of_measurement: h
state_class: measurement
state: >
{% set soc = states('sensor.ioniq_5_ev_battery_level') | float(0) %}
{% set target = states('number.ev_target_soc') | float(80) %}
{% set capacity = 77.4 %} {# usable kWh of your pack #}
{% set amps = states('input_number.ev_max_amps') | float(16) %}
{% set phases = 3 %} {# 1 for single-phase supplies #}
{% set kw = amps * 230 * phases / 1000 * 0.92 %}
{% if soc >= target or kw <= 0 %}
0
{% else %}
{{ (((target - soc) / 100 * capacity) / kw) | round(2) }}
{% endif %}
Then the automation. It polls every five minutes, and once it commits it waits in place until the target is reached, so it cannot re-trigger over itself.
alias: "EV: ready by departure time"
description: Start as late as possible while still hitting the target SoC.
trigger:
- platform: time_pattern
minutes: "/5"
condition:
- condition: state
entity_id: binary_sensor.easee_home_cable_connected
state: "on"
- condition: numeric_state
entity_id: sensor.ioniq_5_ev_battery_level
below: number.ev_target_soc
- condition: template
value_template: >
{% set need = states('sensor.ev_hours_to_target') | float(0) %}
{% set dep = today_at(states('input_datetime.ev_departure')) %}
{% set dep = dep if dep > now() else dep + timedelta(days=1) %}
{{ need > 0
and (dep - now()).total_seconds() / 3600 <= (need + 0.25) }}
action:
- service: number.set_value
target:
entity_id: number.easee_home_dynamic_charger_limit
data:
value: "{{ states('input_number.ev_max_amps') | int(16) }}"
- service: switch.turn_on
target:
entity_id: switch.easee_home_is_enabled
- wait_template: >
{{ states('sensor.ioniq_5_ev_battery_level') | float(0)
>= states('number.ev_target_soc') | float(80) }}
timeout: "10:00:00"
continue_on_timeout: true
- service: switch.turn_off
target:
entity_id: switch.easee_home_is_enabled
mode: single
max_exceeded: silent
The extra 0.25 is fifteen minutes of slack for charging losses and a cold battery. In a Finnish or Danish winter make it 0.75 — a pack at −5 °C accepts current noticeably slower than the arithmetic suggests.
2. Solar-surplus-only charging
This modulates current to track export, with a dead band so it does not oscillate. The trick is in the template sensor: surplus available to the car is current export plus whatever the charger already draws. Without that term the loop drives export to zero, reads zero surplus, and stops.
template:
- sensor:
- name: "Solar Surplus For EV"
unique_id: solar_surplus_for_ev
unit_of_measurement: W
device_class: power
state_class: measurement
# All three must be in watts. HomeWizard P1 reports W;
# the DSMR integration reports kW, so multiply by 1000.
state: >
{% set exp = states('sensor.p1_meter_power_production') | float(0) %}
{% set imp = states('sensor.p1_meter_power_consumption') | float(0) %}
{% set ev = states('sensor.easee_home_power') | float(0) %}
{{ [ (exp - imp) + ev, 0 ] | max | round(0) }}
alias: "EV: solar surplus only"
description: Follows export with a 1 A dead band and a 3 minute stop delay.
trigger:
- platform: time_pattern
minutes: "/1"
condition:
- condition: state
entity_id: input_boolean.ev_solar_only
state: "on"
- condition: state
entity_id: binary_sensor.easee_home_cable_connected
state: "on"
action:
- variables:
surplus: "{{ states('sensor.solar_surplus_for_ev') | float(0) }}"
now_amps: >
{{ states('number.easee_home_dynamic_charger_limit') | float(0) }}
- variables:
# Single-phase: 230 V per amp. For a three-phase charger that
# cannot drop to one phase, divide by (230 * 3) instead.
want_amps: "{{ (surplus / 230) | round(0, 'floor') | int }}"
- choose:
# Too little sun: stop, but only after 3 minutes below the floor.
- conditions:
- condition: template
value_template: "{{ want_amps < 6 }}"
- condition: state
entity_id: switch.easee_home_is_enabled
state: "on"
for: "00:03:00"
sequence:
- service: switch.turn_off
target:
entity_id: switch.easee_home_is_enabled
# Enough sun, and the change is big enough to be worth writing.
- conditions:
- condition: template
value_template: >
{{ want_amps >= 6
and (want_amps - now_amps) | abs >= 1 }}
sequence:
- service: number.set_value
target:
entity_id: number.easee_home_dynamic_charger_limit
data:
value: "{{ [ want_amps, states('input_number.ev_max_amps')
| int(16) ] | min }}"
- service: switch.turn_on
target:
entity_id: switch.easee_home_is_enabled
mode: single
max_exceeded: silent
One hard constraint: Type 2 charging cannot go below 6 A, so a single-phase charger needs ~1.4 kW of surplus to do anything and a three-phase one needs ~4.1 kW. On a 4 kWp array in Belgium or Denmark that floor is only cleared for a few hours around midday in summer. If your charger can switch between one and three phases — some Easee and go-e models can — use it, and the useful window roughly triples.
3. Price-threshold charging
This picks the N cheapest hours remaining before departure, and charges unconditionally whenever price drops under a hard cap — which is how you catch the negative-price hours now common in NL, DE and DK. The binary sensor does the thinking so the automation stays trivial.
template:
- binary_sensor:
- name: "EV Cheap Hour Now"
unique_id: ev_cheap_hour_now
state: >
{% set p = 'sensor.nordpool_kwh_nl_eur_3_10_021' %}
{% set n = states('input_number.ev_cheap_hours') | int(6) %}
{% set cap = states('input_number.ev_price_cap') | float(0.15) %}
{% set hs = now().replace(minute=0, second=0, microsecond=0) %}
{% set dep = today_at(states('input_datetime.ev_departure')) %}
{% set dep = dep if dep > now() else dep + timedelta(days=1) %}
{% set slots = (state_attr(p, 'raw_today') or [])
+ (state_attr(p, 'raw_tomorrow') or []) %}
{% set window = slots
| rejectattr('value', 'none')
| selectattr('start', 'ge', hs)
| selectattr('start', 'lt', dep)
| list %}
{% set cheapest = window
| sort(attribute='value')
| map(attribute='start')
| list %}
{{ (states(p) | float(99) <= cap) or (hs in cheapest[:n]) }}
alias: "EV: charge in the cheapest hours"
description: >
Follows binary_sensor.ev_cheap_hour_now. Swap the Nordpool entity for
the ENTSO-e, Tibber or Energi Data Service price sensor as needed.
trigger:
- platform: state
entity_id: binary_sensor.ev_cheap_hour_now
to: ["on", "off"]
condition:
- condition: state
entity_id: binary_sensor.easee_home_cable_connected
state: "on"
action:
- choose:
- conditions:
- condition: state
entity_id: binary_sensor.ev_cheap_hour_now
state: "on"
- condition: numeric_state
entity_id: sensor.ioniq_5_ev_battery_level
below: number.ev_target_soc
sequence:
- service: number.set_value
target:
entity_id: number.easee_home_dynamic_charger_limit
data:
value: "{{ states('input_number.ev_max_amps') | int(16) }}"
- service: switch.turn_on
target:
entity_id: switch.easee_home_is_enabled
default:
- service: switch.turn_off
target:
entity_id: switch.easee_home_is_enabled
mode: single
In Denmark, add your DSO's hourly grid tariff to the value before sorting. Radius and the other Danish operators charge several times more per kWh between 17:00 and 20:00 than overnight, and that gap is usually wider than the spot spread you are optimising against. Energi Data Service exposes the tariff, so sort on spot plus tariff, not spot alone.
4. Load balancing against the main fuse
The safety net. It reads per-phase current from the P1 meter, subtracts the charger's own contribution to get the house draw, and gives the car whatever is left under the fuse rating minus a reserve. Run it alongside the other three: it only ever lowers the limit, so it composes with them rather than fighting them.
alias: "EV: load balancing (3 x 25 A main fuse)"
description: >
Keeps the worst-loaded phase under the fuse rating. Assumes per-phase
current from a P1 meter and a charger that reports its own draw.
trigger:
- platform: time_pattern
seconds: "/20"
- platform: state
entity_id: switch.easee_home_is_enabled
to: "on"
condition:
- condition: state
entity_id: switch.easee_home_is_enabled
state: "on"
action:
- variables:
fuse_amps: 25
reserve_amps: 2
charger_amps: "{{ states('sensor.easee_home_current') | float(0) }}"
worst_phase: >
{{ [ states('sensor.p1_meter_active_current_l1') | float(0),
states('sensor.p1_meter_active_current_l2') | float(0),
states('sensor.p1_meter_active_current_l3') | float(0) ] | max }}
- variables:
allowed: >
{{ [ [ (fuse_amps - reserve_amps - (worst_phase - charger_amps))
| round(0, 'floor') | int,
states('input_number.ev_max_amps') | int(16) ] | min, 0 ] | max }}
- choose:
# No room left for the 6 A minimum: pause rather than trip the fuse.
- conditions:
- condition: template
value_template: "{{ allowed < 6 }}"
sequence:
- service: switch.turn_off
target:
entity_id: switch.easee_home_is_enabled
# Otherwise track the available headroom, 1 A dead band.
- conditions:
- condition: template
value_template: >
{{ (allowed - (states('number.easee_home_dynamic_charger_limit')
| float(0))) | abs >= 1 }}
sequence:
- service: number.set_value
target:
entity_id: number.easee_home_dynamic_charger_limit
data:
value: "{{ allowed }}"
mode: single
max_exceeded: silent
Test this before you trust it
Load balancing in software is a convenience, not a protection device. Your charger's hardware limit and your fuse box are what keep you safe, so set the charger's built-in circuit limit correctly first and treat this as an optimisation on top. Watch it for a week with the oven, dryer and heat pump running before you trust it.
Getting Started
Here's the recommended path:
- Install your charger integration - Most take under 15 minutes
- Add price data - Nordpool, Tibber, or your provider's integration
- Install EV Smart Charging - From HACS
- Configure basic automation - Set departure times and price thresholds
- Add solar integration - If applicable, prioritize solar excess
- Implement load balancing - Prevent grid overloads
Frequently Asked Questions
Are there any home automation solutions that support EV charging integration?
Yes — Home Assistant is the most capable of them, with native or community integrations for Easee, Zaptec, Wallbox, go-eCharger, Tesla Wall Connector, and any OCPP-compliant charger. Homey and openHAB cover a narrower set of chargers, and vendor apps (Easee, Wallbox, Tesla) each control their own hardware but can't coordinate charging with your tariff or your solar inverter. If you want charging that reacts to electricity price and solar surplus together, Home Assistant is the practical choice because it is the only one that already holds all three data sources.
How do I set up an EV charger automation in Home Assistant?
Add your charger's integration so you have a switch or number entity that starts charging and sets the current limit. Then add a price source (Nordpool, ENTSO-e, or Tibber) and write an automation that triggers on the cheapest hours and calls switch.turn_on on the charger, with a condition that the car is actually plugged in. Most people's first working version is about fifteen lines of YAML; solar priority and load balancing are refinements you layer on afterwards.
How does EV smart charging work in Home Assistant?
The EV Smart Charging custom integration (installed via HACS) takes your departure time, the target state of charge, and an hourly price sensor, then solves for the cheapest contiguous or split window that still finishes in time. It exposes a binary sensor you use as the charging condition, so Home Assistant switches the charger on and off for you overnight. Because it works from a price sensor rather than the charger's own scheduler, it keeps working if you change energy supplier or swap the charger.
Is evcc a better alternative than Home Assistant for EV charging?
For charging alone, usually yes. evcc is purpose-built and ships with phase switching, per-vehicle profiles and a charge plan you would otherwise have to write yourself. Home Assistant wins when charging is one consumer among several and something has to arbitrate between the car, the heat pump, the hot water cylinder and a home battery all competing for the same cheap hours and the same solar surplus. The two are not mutually exclusive: evcc runs as a Home Assistant OS add-on and reports back over MQTT, so a common setup is evcc controlling the charger while Home Assistant handles the rest of the house.
Can I do smart charging without an integration for my car?
Yes. Charge during the cheapest hours using a price sensor plus the charger's cable-connected binary sensor, and use the charger's own kWh counter to decide when to stop instead of the car's state of charge. You give up charging to an exact percentage, but you keep the whole price saving and you avoid polling a rate-limited manufacturer API that wakes the car and drains its 12 V battery.
Complete EV + Energy System
Our Energy Dashboard Guide includes normalized price and energy data, cost dashboards, 16 production-ready home-energy automations, and 4 reusable patterns. It does not include vendor-specific EV charger control or battery control. 120+ pages.
Get the Guide — €2930-day money-back guarantee. No questions asked.