Let’s go into details!

Finished product
What It Might Look Like

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

Electric diagram
The Plans

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.