Let’s go into details!

If you’ve been reading my DIY EV Charging Point Basics post, you probably wonder how all the options mentioned might be distilled into a practical DIY project. This post describes a charging point I actually built. I will explain why and how I made the individual choices, list an actual BOM, describe how everything is connected and even give you the configuration files I used in the first version.
Design Objectives
Some Background
I built this charging point for a neighbor friend who recently, after some deliberation, decided to start driving an EV in the best possible way for “beginners”: He bought a used short-range EV he uses as the “second car” to drive to work and do errands (he has a big family, so there are many errands to take care of). This saves him a ton of gas money and the objectively better driving experience is simply the icing on the cake.
Until now, he was using the charging cable he got with the car, but this was slow (only half the available charging power) and he had no handy sockets to plug it in, forcing him to use an extension cord. So we talked and I offered to help him build a proper Type 2 charging point, enabling him to use the full charging power and avoid dealing with the “cable salad”, while having everything properly prepared for the next EV that is sure to come in time. We sat down, talked and decided what to build.
Parameters
- He does not have a smart home solution, so the solution had to be standalone.
- We decided it would be best if we installed it on the outside wall of his house, next to his driveway.
- To keep the charging point visually clean, we would use a socket, not a cable.
- Although his present car only has a one-phase internal charger, we decided to build a full three-phase charging point, since the difference in costs is minimal and we wanted to keep everything future-proof.
- With 3x 20A distribution fuses, dynamic current control was a must.
- He needs minimal access control, a switch in the house was enough.
- There should be some easy way to charge only at night, when energy is cheaper. But there should also be an option to charge at any time to top up the car when needed.
Design Choices
After the parameters were set, it was relatively easy to decide on the final solution:
- I decided to use the SimpleEVSE controller to take care of the SAE J1772 standard signalling and control since I had good experience using it in the past.
- I would use a ESPHome controller, connected to the WiFi to control the EVSE controller (current and time control, monitoring).
- The charging point would have it’s own weatherproof switchboard on the outside wall, containing the fuse, relay, EVSE controller and the ESPHome controller. The socket would be built right next to it.
- He had some space left in an auxiliary switchboard inside the house, so we would put the RCD switch there, so it can also serve as an access control switch to turn off the whole charging point when needed.
- We would install a cheap smart electricity meter in the main switchboard to measure the currents on distribution fuses. Since this switchboard is on the other side of the house and wiring an extra data transfer cable to the charging point would be a problem, I decided to read it using a separate ESPHome controller connected to the home WiFi network.
The Actual Build

In our case, the system is housed in two small boxes, but if your installation allows, you could connect the energy counter directly to the charge control ESPHome controller, saving yourself one extra ESPHome controller and getting better reliability.
BOM:
Current Supervisor:
- Wiring box as your situation requires
- An energy meter with Modbus interface, e.g. this one or this one
- RS485 to 3.3V converter, e.g. this
- ESP8266 or ESP32 module, e.g. this
- A 5V power supply
Charge Control Unit:
- Wiring box
- RS485 to 3.3V converter, e.g. this
- ESP8266 or ESP32 module, e.g. this
- 12V to 5V DC-DC converter, e.g. this
- EVSE DIN controller, buy it here, the have many other parts for this project too
- 4-pole contactor, like this one
- A RCD, type B, like this one
- A MCB (fuse), 16A B-type in our case
- A 4P T2 surge protecting device (SPD), like this one
- A type 2 socket, like this one
Build Instructions
Use the schematic as your guide. Be careful about proper grounding, correct wire and component sizing, weatherproofing, wiring screw torques etc. DIN-rails are your friend.
You should have enough experience with electric projects to be able to build and customize this from the data I provided. If you don’t, it’s likely you are not able to build it safely on your own, so talk to people who understand it better.
Example Code for the Two ESPHome Controllers
Current Supervisor:
esphome:
name: stevec
esp8266:
board: d1_mini
# Disable logging to be able to use Modbus
logger:
baud_rate: 0
ota:
wifi:
networks:
—ssid: "ssid"
password: "pass"
web_server:
port: 80
uart:
id: mod_bus
tx_pin: GPIO1
rx_pin: GPIO3
baud_rate: 9600
stop_bits: 1
parity: even
modbus:
id: modbus_stevec
send_wait_time: 200ms
flow_control_pin: GPIO5
modbus_controller:
—id: stevec
address: 24
modbus_id: modbus_stevec
update_interval: 5s
setup_priority: -10
sensor:
#Template sensor
—platform: template #Max phase current, will be written by lambda
name: "TokMax"
id: tokmax
#Readouts of individual phase currents from the smart meter
—platform: modbus_controller
modbus_controller_id: stevec
id: tokr
name: "TokR"
address: 0x0008
unit_of_measurement: "A"
register_type: "read"
value_type: FP32
accuracy_decimals: 1
filters:
—sliding_window_moving_average:
window_size: 6
—platform: modbus_controller
modbus_controller_id: stevec
id: toks
name: "TokS"
address: 0x000A
unit_of_measurement: "A"
register_type: "read"
value_type: FP32
accuracy_decimals: 1
filters:
—sliding_window_moving_average:
window_size: 6
—platform: modbus_controller
modbus_controller_id: stevec
id: tokt
name: "TokT"
address: 0x000C
unit_of_measurement: "A"
register_type: "read"
value_type: FP32
accuracy_decimals: 1
filters:
—sliding_window_moving_average:
window_size: 6
time:
—platform: sntp
id: sntp_time
on_time:
# Every 10 seconds do readout of max phase current
—seconds: /10
then:
—lambda: |-
id(tokmax).publish_state((id(tokr).state > id(toks).state) ? ((id(tokr).state > id(tokt).state) ? id(tokr).state : id(tokt).state) : ((id(toks).state > id(tokt).state) ? id(toks).state : id(tokt).state));
This is simple: We talk to the Modbus electricity meter every 10 seconds to get the three currents. We do a moving average to keep track of fast current changes. The result (largest average current) is available over HTTP API.
Charge Controller:
esphome:
name: evse
esp8266:
board: d1_mini
# Disable logging to be able to use Modbus
logger:
baud_rate: 0
ota:
http_request:
useragent: esphome/ESP8266
timeout: 10s
esp8266_disable_ssl_support: yes #to prevent OTA filing
id: http_request_data
wifi:
networks:
—ssid: "ssid"
password: "pass"
web_server:
port: 80
uart:
id: mod_bus
tx_pin: GPIO1
rx_pin: GPIO3
baud_rate: 9600
modbus:
id: modbus_evse
modbus_controller:
—id: evse
address: 0x01
modbus_id: modbus_evse
update_interval: 30s
setup_priority: -10
sensor:
—platform: template #Max phase current, will be written by lambda
name: "TokMax"
id: tokmax
#EVSE sensors
—platform: modbus_controller
modbus_controller_id: evse
id: set_charge_current
register_type: holding
address: 1000
value_type: U_WORD
name: Set Charge Current
unit_of_measurement: A
device_class: current
state_class: measurement
accuracy_decimals: 0
—platform: modbus_controller
modbus_controller_id: evse
id: vehicle_state
register_type: holding
address: 1002
value_type: U_WORD
name: Vehicle State
accuracy_decimals: 0
—platform: modbus_controller
modbus_controller_id: evse
id: ctrl_bits
register_type: holding
address: 1004
value_type: U_WORD
name: Control Bits
entity_category: diagnostic
disabled_by_default: true
—platform: modbus_controller
modbus_controller_id: evse
id: evse_state
register_type: holding
address: 1006
value_type: U_WORD
name: EVSE State
accuracy_decimals: 0
#Manual current setting slider
number:
—platform: modbus_controller
modbus_controller_id: evse
id: set_charge_current_input
address: 1000
value_type: U_WORD
name: Set Charge Current (Input)
unit_of_measurement: A
min_value: 0
max_value: 16
step: 1
use_write_multiple: True
write_lambda: |-
if (x > 0 && x < 6) {
return 6;
}
return x;
#Switch for turning EVSE on or off. Note the negated function: switch on = EVSE off!
switch:
—platform: modbus_controller
modbus_controller_id: evse
id: disable_charging
name: Disable Charging
use_write_multiple: True
register_type: holding
address: 2005
bitmask: 16384
#Template binary sensor for enabling/disabling the whole EVSE system, used for time scheduling to reduce electricity costs
—platform: template
name: Enable
id: enable
restore_state: true
optimistic: true
turn_on_action:
—switch.turn_off: disable_charging
turn_off_action:
—switch.turn_on: disable_charging
#Readouts and automation
time:
—platform: sntp
id: sntp_time
on_time:
# Every 30 seconds do readout of max phase current
—seconds: /30
then:
—http_request.get:
url: http://stevec.local/sensor/tokmax
headers:
Content-Type: application/json
verify_ssl: false
on_response:
then:
—lambda: |-
json::parse_json(id(http_request_data).get_string(), [](JsonObject root) {
id(tokmax).publish_state(root["value"]);
});
# Every 5 minutes do current control
—seconds: 0
minutes: /2
then:
—lambda: |-
//Current limits of system
const float max_phase_current=20, max_charge_current=16, min_charge_current=6;
//Only do current control if EVSE is ON
if (!id(disable_charging).state) {
//Only do current control if max phase current is too high or charge current setting is reduced (plus offset to prevent corrections under 1A, which have no effect)
if ((id(tokmax).state > (max_phase_current + 0.5))||(id(set_charge_current).state < max_charge_current)) {
//If max phase current is too high (plus offset)
if (id(tokmax).state > (max_phase_current + 0.5)) {
//If current is low enough to limit properly
if (id(tokmax).state < (max_phase_current + id(set_charge_current).state—min_charge_current)) {
//Reduce current setting by exactly the excess current
auto call = id(set_charge_current_input).make_call();
call.set_value(id(set_charge_current).state—(id(tokmax).state-max_phase_current));
call.perform();
} else {
//Turn off EVSE to cool everything off
id(disable_charging).turn_on();
}
} else if (id(set_charge_current).state < max_charge_current) {
//Increase current setting by exactly the unused current capacity
if ((id(set_charge_current).state—(id(tokmax).state-max_phase_current)) < max_charge_current) {
auto call = id(set_charge_current_input).make_call();
call.set_value(int (id(set_charge_current).state—(id(tokmax).state-max_phase_current)));
call.perform();
} else {
//Or set maximum charge current
auto call = id(set_charge_current_input).make_call();
call.set_value(max_charge_current);
call.perform();
}
}
}
}
# Every 15 minutes turn EVSE back on
—seconds: 0
minutes: /15
then:
—if:
condition:
lambda: |-
return id(enable).state;
then:
—switch.turn_off: disable_charging
This is the main functionality. We set up the Modbus communication with the EVSE controller and prepare the necessary variables to control it. My neighbor did not want to have the whole Home Assistant setup, so the control works via a slider in the inbuilt web server.
The control principle is simple – if the average max current exceeds the permitted maximum, the charging current is set to a lover value. E.g. with 20A limit, if the actual phase current is 22A and the set charging current is 16A, the charging current is reduced by 22-20 = 2A, e.g. to 14A. If there is headroom (a big load has disconnected), the charging current is set higher in the same way. This repeats every 5 minutes.
In extreme cases, where the charging current would have to be reduced under 6A (minimum for the EVSE standard), the charging point is turned off until the next 15 minute interval. This should cool off the fuses enough. Keep in mind that normal fuses should hold a significant overload for tens of minutes (standard requirement is: blow in less than one hour for 45% overload, over one hour for 13% overload).
All that is left is a simple option to turn the whole thing on or off via a switch in the web server or an API call.
Final Notes
This was intended as an illustration of how to build a safe EV charging point with dynamic current control. There are literally thousands of ways how you could go about achieving this, but the general outline will always be the same:
Measure current coming into your home, compare to limit, set charging current, repeat. Turn off EVSE for 15 minutes, if load is too damn high 😉
This could be thought of as trivial, but there is an important point here: if you buy a ready-made charging point, it needs to dynamically control the charging current! If you buy a “dumb” charging point, you are going to be paying for blown fuses at least some of the time and extra power capacity all the time.
If you found this article useful, you can buy me a beer here.