-
-
Notifications
You must be signed in to change notification settings - Fork 6
ev optimal charging template
Note: This is a legacy Home Assistant template sensor provided as a user reference. HSEM now includes a native EV planner (
hsem-ev-charge-planservice) that handles optimal charging natively. This template is kept for users who prefer a standalone template-based approach.
This page describes a cost‑optimal charging plan template sensor for Home Assistant based on:
- Household consumption
- Solar production (net consumption)
- Dynamic energy prices and tariffs
- Your EV's current state of charge and charging deadline
The sensor exposes:
- A simple state you can use in automations (
charging,waiting,not_connected, etc.) - A detailed
charging_slotsattribute with per‑slot cost and solar data
You need the following entities in Home Assistant:
| Entity | Purpose |
|---|---|
binary_sensor.go_echarger_222819_car |
on when the car is connected to the charger |
sensor.audi_e_tron_state_of_charge |
Current SoC in percent |
input_number.audi_e_tron_charging_target |
Target SoC (e.g., 80) |
input_datetime.audi_e_tron_charge_end_time |
Latest time the car must be ready |
input_boolean.audi_e_tron_smart_charging |
Smart charging toggle |
sensor.hsem_workingmode_sensor |
HSEM working mode sensor with hourly_recommendations
|
The hourly_recommendations items must contain at least:
-
startandend(datetimes) -
import_price(price per kWh including tariffs) -
estimated_net_consumption(kWh, house load minus solar, negative = surplus)
The template sensor solves one problem:
"Given my current SoC, target SoC, deadline, prices, and solar forecast, in which time slots should I charge to minimize imported energy cost?"
It does this by:
- Estimating how many kWh your EV needs to reach the target SoC
- Looking at all future slots between now and your deadline
-
Calculating for each slot:
- How many kWh the car can take in that slot
- How much of that is covered by solar surplus
- How much must be imported from the grid
- What the cost of that import will be
- Sorting the slots by effective cost
- Picking the cheapest slots until the required kWh are covered
-
Exposing those as
charging_slotsand switching state betweenchargingandwaiting
The plan:
- Prefers slots with solar surplus
- Prefers cheap grid prices
- Respects your end‑time deadline
- Adjusts dynamically as SoC or forecasts change
The template creates one sensor:
sensor.hsem_ev_optimal_charging_plan
State (string):
| State | Meaning |
|---|---|
not_connected |
Car not plugged in |
smart_charging_disabled |
Smart charging boolean is off |
fully_charged |
Current SoC ≥ target SoC |
charging |
Inside a selected charging slot |
waiting |
Connected and not full, but outside selected slots |
Attributes:
| Attribute | Type | Description |
|---|---|---|
smart_charging |
boolean | Smart charging toggle state |
battery_capacity_kwh |
float | Fixed EV battery capacity |
charge_power_kw |
float | Fixed charging power |
current_soc |
float | Current state of charge |
target_soc |
float | Target state of charge |
ev_connected |
boolean | EV connection status |
total_kwh_needed |
float | Energy needed to reach target |
deadline |
datetime | Latest charging deadline |
charging_slots |
list | Planned charging slots (see below) |
Each charging_slots item:
{
"start": "2026-03-10T01:00:00+01:00",
"end": "2026-03-10T01:15:00+01:00",
"import_price": 0.75,
"solar_surplus_kwh": 1.2,
"import_needed_kwh": 0.4,
"estimated_charged_kwh": 1.6,
"estimated_cost": 0.30
}Add this to your configuration.yaml (or template: include file). Adjust entity IDs, battery_capacity_kwh, and charge_power_kw to match your setup.
template:
- trigger:
- trigger: time_pattern
seconds: /5
- trigger: state
entity_id:
- input_boolean.audi_e_tron_smart_charging
- binary_sensor.go_echarger_222819_car
to:
sensor:
- name: "HSEM EV Optimal Charging Plan"
unique_id: hsem_ev_optimal_charging_plan
state: >-
{%- set ev_connected = is_state('binary_sensor.go_echarger_222819_car', 'on') %}
{%- if not ev_connected %}
not_connected
{%- else %}
{%- set smart_charging = is_state('input_boolean.audi_e_tron_smart_charging', 'on') -%}
{%- if not smart_charging -%}
smart_charging_disabled
{%- else %}
{%- set current_soc = states('sensor.audi_e_tron_state_of_charge') | float(0) %}
{%- set target_soc = states('input_number.audi_e_tron_charging_target') | float(80) %}
{%- if current_soc >= target_soc %}
fully_charged
{%- else %}
{%- set now_ts = now().timestamp() %}
{%- set slots = state_attr('sensor.hsem_ev_optimal_charging_plan', 'charging_slots') %}
{%- set ns = namespace(active=false) %}
{%- if slots %}
{%- for slot in slots %}
{%- set slot_start = as_datetime(slot.start).timestamp() %}
{%- set slot_end = as_datetime(slot.end).timestamp() %}
{%- if now_ts >= slot_start and now_ts < slot_end %}
{%- set ns.active = true %}
{%- endif %}
{%- endfor %}
{%- endif %}
{{ 'charging' if ns.active else 'waiting' }}
{%- endif %}
{%- endif %}
{%- endif %}
attributes:
smart_charging: >-
{{ is_state('input_boolean.audi_e_tron_smart_charging', 'on') }}
battery_capacity_kwh: "86.5"
charge_power_kw: "10.6"
current_soc: >-
{{ states('sensor.audi_e_tron_state_of_charge') | float(0) }}
target_soc: >-
{{ states('input_number.audi_e_tron_charging_target') | float(80) }}
ev_connected: >-
{{ is_state('binary_sensor.go_echarger_222819_car', 'on') }}
total_kwh_needed: >-
{%- set current_soc = states('sensor.audi_e_tron_state_of_charge') | float(0) %}
{%- set target_soc = states('input_number.audi_e_tron_charging_target') | float(80) %}
{%- set battery_capacity_kwh = 86.5 %}
{{ [((target_soc - current_soc) / 100) * battery_capacity_kwh, 0] | max | round(2) }}
deadline: >-
{%- set now_ts = now().timestamp() %}
{%- set end_time = states('input_datetime.audi_e_tron_charge_end_time') %}
{%- set deadline_today = today_at(end_time) %}
{%- set deadline_ts = deadline_today.timestamp() if deadline_today.timestamp() > now_ts else (deadline_today.timestamp() + 86400) %}
{{ deadline_ts | timestamp_local }}
charging_slots: >-
{%- set ev_connected = is_state('binary_sensor.go_echarger_222819_car', 'on') %}
{%- set current_soc = states('sensor.audi_e_tron_state_of_charge') | float(0) %}
{%- set target_soc = states('input_number.audi_e_tron_charging_target') | float(80) %}
{%- set smart_charging = is_state('input_boolean.audi_e_tron_smart_charging', 'on') %}
{%- if smart_charging and (not ev_connected or current_soc >= target_soc) %}
[]
{%- else %}
{%- set battery_capacity_kwh = 86.5 %}
{%- set charge_power_kw = 10.6 %}
{%- set recommendation_interval_minutes = state_attr('sensor.hsem_workingmode_sensor', 'recommendation_interval_minutes') | int(15) %}
{%- set slot_duration_h = recommendation_interval_minutes / 60 %}
{%- set kwh_per_slot = charge_power_kw * slot_duration_h %}
{%- set total_needed_kwh = ((target_soc - current_soc) / 100) * battery_capacity_kwh %}
{%- set now_ts = now().timestamp() %}
{%- set end_time = states('input_datetime.audi_e_tron_charge_end_time') %}
{%- set deadline_today = today_at(end_time) %}
{%- set deadline_ts = deadline_today.timestamp() if deadline_today.timestamp() > now_ts else (deadline_today.timestamp() + 86400) %}
{%- set recs = state_attr('sensor.hsem_workingmode_sensor', 'hourly_recommendations') %}
{%- if recs %}
{%- set ns = namespace(candidates_future=[], candidates_all=[]) %}
{%- for slot in recs %}
{%- set slot_start = as_datetime(slot.start).timestamp() %}
{%- set slot_end = as_datetime(slot.end).timestamp() %}
{%- if slot_end > now_ts and slot_end <= deadline_ts %}
{%- set start_str = as_datetime(slot.start).strftime('%Y-%m-%dT%H:%M:%S%z') %}
{%- set end_str = as_datetime(slot.end).strftime('%Y-%m-%dT%H:%M:%S%z') %}
{%- set net = slot.estimated_net_consumption | float(0) %}
{%- set solar_surplus = [(-net), 0] | max %}
{%- set minutes_remaining = ((slot_end - now_ts) / 60) | round(0, 'ceil') | int %}
{%- set fraction = [minutes_remaining / recommendation_interval_minutes, 1] | min %}
{%- set kwh_this_slot = kwh_per_slot * fraction %}
{%- set import_needed_kwh = [kwh_this_slot - solar_surplus, 0] | max %}
{%- set effective_cost = slot.import_price | float %}
{%- set kandidat = {
'start': start_str,
'end': end_str,
'import_price': slot.import_price | float,
'solar_surplus': solar_surplus | round(3),
'kwh_this_slot': kwh_this_slot | round(3),
'import_needed_kwh': import_needed_kwh | round(3),
'effective_cost': effective_cost | round(3)
} %}
{%- if slot_end > now_ts %}
{%- set ns.candidates_future = ns.candidates_future + [kandidat] %}
{%- endif %}
{%- set ns.candidates_all = ns.candidates_all + [kandidat] %}
{%- endif %}
{%- endfor %}
{%- set future_kwh = ns.candidates_future | sum(attribute='kwh_this_slot') %}
{%- set candidates = ns.candidates_future if future_kwh >= total_needed_kwh else ns.candidates_all %}
{%- set sorted = candidates | sort(attribute='effective_cost') %}
{%- set ns2 = namespace(result=[], kwh_remaining=total_needed_kwh) %}
{%- for slot in sorted %}
{%- if ns2.kwh_remaining > 0 %}
{%- set actual_cost = [slot.kwh_this_slot - slot.solar_surplus, 0] | max * slot.import_price %}
{%- set ns2.result = ns2.result + [{
'start': slot.start,
'end': slot.end,
'import_price': slot.import_price,
'solar_surplus_kwh': slot.solar_surplus,
'import_needed_kwh': slot.import_needed_kwh,
'estimated_charged_kwh': slot.kwh_this_slot | round(3),
'estimated_cost': actual_cost | round(3)
}] %}
{%- set ns2.kwh_remaining = ns2.kwh_remaining - slot.kwh_this_slot %}
{%- endif %}
{%- endfor %}
{{ ns2.result | sort(attribute='start') }}
{%- else %}
[]
{%- endif %}
{%- endif %}-
Total energy needed — uses current SoC, target SoC, and
battery_capacity_kwh. Example: SoC 40 → target 80 on 86.5 kWh battery →((80 − 40) / 100) × 86.5 ≈ 34.6 kWh -
Deadline handling — reads
input_datetime. If today's time has passed, shifts deadline to tomorrow (+86400 seconds). -
Slot selection window — takes all
hourly_recommendationswhere slot end is after now and before/at deadline. -
Solar and net consumption —
solar_surplus = max(-net, 0) -
Slot charging capacity —
kwh_per_slot = charge_power_kw × (interval_minutes / 60). If slot is partially passed, scales by remaining minutes. -
Import need and cost —
import_needed_kwh = max(kwh_per_slot − solar_surplus, 0),estimated_cost = import_needed_kwh × import_price -
Optimal schedule — sorts slots by
effective_cost, picks cheapest until total kWh ≥ needed, outputs sorted bystart.
automation:
- alias: "HSEM EV Smart Charging"
mode: restart
trigger:
- platform: state
entity_id:
- sensor.hsem_ev_optimal_charging_plan
condition:
- condition: state
entity_id: input_boolean.audi_e_tron_smart_charging
state: "on"
- condition: state
entity_id: binary_sensor.go_echarger_222819_car
state: "on"
action:
- choose:
- conditions:
- condition: state
entity_id: sensor.hsem_ev_optimal_charging_plan
state: "charging"
sequence:
- service: switch.turn_on
target:
entity_id: switch.go_echarger_222819_relay
- conditions:
- condition: state
entity_id: sensor.hsem_ev_optimal_charging_plan
state: "waiting"
sequence:
- service: switch.turn_off
target:
entity_id: switch.go_echarger_222819_relayYou can extend this with extra conditions (night‑only charging, max amps, etc.).
- Home — User-facing overview: features, FAQ, working modes, battery schedules, excess export, consumption sensors
- Battery Charging Economics — How to calculate the minimum charging price for a battery schedule
- Architecture Overview — System context, layered architecture, module map, planning pipeline
- Planner Specification — Normative — all planner invariants, rules, and constraints
- Planner Technical Guide — How the planner works with worked examples
- Cost Function Math — Complete mathematical formulation of the 8-term cost function
- Energy Accounting — Physical energy flow model, SoC simulation, efficiency math
- Candidate Generation — How candidates are generated, assumptions, partial-SoC
- MILP Optimization — Full LP formulation, variable layout, constraints, and solver pipeline
- Consumption Prediction — Weighted-average model, IQR outlier detection, spike suppression
- Safety Modes — Degraded mode, read-only gate, write-verify applier, runtime resolver
- Price Scaling — EDS price scaling, eds_share conversion factor
- Services Reference — All 4 HSEM services with examples
- Sensors Reference — Complete entity reference: all sensor, select, switch, number, and time entities
- Dashboard Setup — Step-by-step ApexCharts dashboard with full YAML, layout reference, and troubleshooting
- Config Flow Reference — Every config/options flow step and field
- EV Charge Plan Setup — EV planned load configuration guide
- EV Surplus Charging Automation — Wire your physical EV charger (go-e, Easee, Zaptec) to follow HSEM surplus recommendations
- EV Optimal Charging Template — Legacy Home Assistant template sensor for cost-optimal EV charging
- Forecast Accuracy Tracking — Forecast vs actual tracking system
- Huawei Entities — Canonical HA entity ID reference
- Troubleshooting Guide — Diagnose and fix common problems: missing data, wrong prices, write failures, battery behaviour
- Quality Checks — Static quality tools and CI configuration