Siddhant Kumar
Project 071 · Energy

EV Charging Controller.

Charges an EV as fast as the house wiring safely allows — dynamically sharing capacity with the home's other loads — and meters each session for billing, all via the standard charging protocol.

Advanced 14–22 hours 31 min read EVChargingBilling
Jump to source Bill of materials
EV Charging Controller — reference build illustration MCU VCC · GND · SIG · NC
Difficulty
Advanced
Build time
14–22 hours
Indicative cost
₹8,000 – ₹14,000
Platform
ESP32 DevKit V1 (ESP-WROOM-32)
Category
Energy
Last updated
28 July 2026
Contents — 26 sections

Project Overview

Charges an EV as fast as the house wiring safely allows — dynamically sharing capacity with the home's other loads — and meters each session for billing, all via the standard charging protocol.

Charging an electric vehicle at home or at a shared charge point raises a problem a normal socket does not: an EV draws a large, sustained current for hours, and if it does so on top of the house's other loads it can overload the wiring or trip the main breaker. The naive fix — a fixed, low charge current — wastes time and capacity. The smart fix, and the heart of this project, is dynamic load management: the controller continuously measures how much of the supply the rest of the building is using and charges the EV with whatever capacity is left, so the car charges as fast as is safe right now, and automatically backs off when the house load rises (the kettle goes on) and speeds up when it falls.

It does this through the standard EV charging protocol. AC EV charging (IEC 61851 / SAE J1772, the "Type 1/Type 2" connectors) uses a control pilot signal — a PWM waveform whose duty cycle tells the car how much current it may draw. So the controller does not switch the car's current directly; it advertises a safe limit via the pilot, and the car obeys it. By varying that pilot duty cycle in response to the measured spare capacity, the controller safely modulates the charge rate in real time, and it follows the protocol's handshake (detecting the vehicle, confirming readiness, energising the contactor) so it interoperates with standard EVs.

On top of safe charging it adds metering and billing: it measures the energy delivered each session (real power/energy) and logs it per user/session, which is what a shared or workplace charge point needs to bill fairly. It reports status and energy to a dashboard, supports authorisation (who may charge), and schedules charging to cheap tariff windows where desired. This is genuinely safety-critical, high-power equipment, so the project is emphatic: EV charging is governed by standards and electrical codes, the power wiring and protection (RCD/RCBO, earthing) must be done by a qualified electrician, and a DIY controller must respect the protocol and safety interlocks — it is an educational build of the control logic, not a certified charger. But as a load-managing, protocol-correct, metered charging controller, it demonstrates exactly how smart EV charging works: fast when it can be, safe always, and accounted for.

An electric car connected to a charging station
Smart EV charging shares the supply so the car charges as fast as the wiring safely allows. Photograph sourced from Wikimedia Commons — Electric car charging.jpg. Reused under the licence stated on that page; please check it before republishing.

What this project does

  • Charges an EV via the standard control-pilot protocol (IEC 61851/J1772)
  • Dynamically shares supply capacity with the home's other loads
  • Advertises a safe current limit to the car via the pilot PWM duty cycle
  • Follows the charging handshake (vehicle detect, ready, contactor)
  • Meters energy per session for billing
  • Supports authorisation and cheap-tariff scheduling
  • Respects safety standards and interlocks (educational scope)

Real-World Applications

SettingHow it is used
Home EV chargingCharging as fast as the house wiring safely allows without tripping the main breaker.
Shared / apartment charge pointsLoad-managed charging with per-session metering and billing.
Workplace chargingAuthorised, metered charging across multiple bays sharing supply capacity.
Solar-aware chargingCharging preferentially from spare solar/cheap tariff windows.

Deployment contexts where a build of this kind earns its keep.

Features & Capabilities

  • Dynamic load management (charge with spare capacity)
  • Standard control-pilot current signalling
  • Protocol-correct handshake and interoperability
  • Per-session energy metering for billing
  • Authorisation and tariff scheduling
  • Backs off/speeds up as house load changes
  • Explicit safety-standard framing (not a certified charger)

Difficulty, Time & Required Skills

AttributeValue
Difficulty levelAdvanced
Estimated completion time14–22 hours
Indicative build cost₹8,000 – ₹14,000
Primary disciplineEnergy
Reference platformESP32 DevKit V1 (ESP-WROOM-32)

Skills you should have (or will pick up)

  • EV charging protocol (control pilot PWM, handshake)
  • Dynamic load management from whole-supply measurement
  • Contactor control and safety interlocks
  • Per-session energy metering and billing
  • Authorisation and scheduling

Bill of Materials

Every part below is commonly available from Indian and international hobby-electronics suppliers. Prices are indicative 2026 retail figures in Indian rupees and will drift — treat them as a budgeting guide, not a quotation.

ComponentKey specificationQtyApprox. cost
ESP32 DevKit V1 (ESP-WROOM-32)
Wi-Fi transmit bursts peak near 500 mA — size the regulator accordingly.
Dual-core Xtensa LX6 @ 240 MHz, 520 KB SRAM, 4 MB flash, Wi-Fi 802.11 b/g/n + BLE 4.2, 34 GPIO, 18× 12-bit ADC, 2× 8-bit DAC1₹450
PZEM-004T v3 AC energy meter (100 A CT)
The measurement side sits at mains potential — the opto-isolated UART is the only safe boundary.
80–260 VAC, 0–100 A, voltage/current/power/energy/PF/frequency, ±0.5 %1₹950
ACS712 hall-effect current sensor (20 A)
Zero-offset drifts with temperature — re-zero at boot with no load.
±20 A, 100 mV/A, 80 kHz bandwidth, 1.2 mΩ internal resistance, 2.1 kV isolation1₹180
5 V single-channel opto-isolated relay module
Cut the JD-VCC jumper and feed the coil separately for true isolation.
SPDT contacts rated 10 A @ 250 VAC / 10 A @ 30 VDC, opto-isolated input1₹90
0.96″ SSD1306 OLED display
Static images burn in — invert or scroll the screen periodically.
128 × 64 monochrome, 1.3–3.3 V logic, 100 kHz–400 kHz I²C1₹250
MFRC522 13.56 MHz RFID reader + card + fob
Strictly 3.3 V — 5 V on the SPI pins kills the chip.
ISO/IEC 14443A, MIFARE Classic 1K, 0–60 mm range, up to 10 Mbit/s SPI1₹180
5 V 3 A regulated SMPS adapter
Measure the real output — many "3 A" adapters sag below 4.7 V at 2 A.
100–240 VAC in, 5 V ±5 % out, 3 A, short-circuit and over-voltage protection1₹350
EVSE power stage (contactor + pilot/PP)
Safety-critical; must meet the standard and be professionally installed
Contactor rated for the charge current, control-pilot & proximity circuitry per IEC 618511₹3,000
Whole-supply CTCT on the incoming supply to measure total house load for dynamic management1₹500
Session energy meterReal-power/energy metering of the charge for billing1₹700
RCD/RCBO + earthing (installer)
Legally/technically required protection
Required protection for EV charging, installed by a qualified electrician1₹2,000

Estimated total: ₹8,650, excluding tools, shipping and consumables.

Tools and consumables

  • Soldering iron (temperature controlled, 350 °C) with 0.8 mm 60/40 or lead-free solder
  • Digital multimeter — continuity, DC volts and current ranges
  • Wire strippers, flush cutters and a small set of precision screwdrivers
  • Heat-shrink tubing and a heat gun (or a lighter, carefully)
  • A laptop with a USB port and the toolchain listed above

Hardware Specifications

PartSpecificationSupplyInterfaceReference
ESP32 DevKit V1 (ESP-WROOM-32)Dual-core Xtensa LX6 @ 240 MHz, 520 KB SRAM, 4 MB flash, Wi-Fi 802.11 b/g/n + BLE 4.2, 34 GPIO, 18× 12-bit ADC, 2× 8-bit DAC3.3 V logic / 5 V USBUART, SPI, I²C, I²S, CAN, PWMDatasheet
PZEM-004T v3 AC energy meter (100 A CT)80–260 VAC, 0–100 A, voltage/current/power/energy/PF/frequency, ±0.5 %5 V logic sideUART Modbus-RTU 9600Datasheet
ACS712 hall-effect current sensor (20 A)±20 A, 100 mV/A, 80 kHz bandwidth, 1.2 mΩ internal resistance, 2.1 kV isolation5 VAnalogueDatasheet
5 V single-channel opto-isolated relay moduleSPDT contacts rated 10 A @ 250 VAC / 10 A @ 30 VDC, opto-isolated input5 V coilDigital (active-low)Datasheet
0.96″ SSD1306 OLED display128 × 64 monochrome, 1.3–3.3 V logic, 100 kHz–400 kHz I²C3.3–5 VI²C (0x3C)Datasheet
MFRC522 13.56 MHz RFID reader + card + fobISO/IEC 14443A, MIFARE Classic 1K, 0–60 mm range, up to 10 Mbit/s SPI2.5–3.6 VSPI (also I²C/UART)Datasheet
5 V 3 A regulated SMPS adapter100–240 VAC in, 5 V ±5 % out, 3 A, short-circuit and over-voltage protection5 VDC barrel / USBDatasheet

Consolidated electrical and interface specifications for every active part in the build.

Power Budget & Supply Sizing

Add up the typical active current of every part, then size the supply with at least 50 % headroom so transmit bursts and motor inrush never brown out the controller.

LoadSupply railTypical current (mA)Notes
ESP32 DevKit V1 (ESP-WROOM-32)3.3 V logic / 5 V USB160Wi-Fi transmit bursts peak near 500 mA — size the regulator accordingly.
PZEM-004T v3 AC energy meter (100 A CT)5 V logic side20The measurement side sits at mains potential — the opto-isolated UART is the only safe boundary.
ACS712 hall-effect current sensor (20 A)5 V10Zero-offset drifts with temperature — re-zero at boot with no load.
5 V single-channel opto-isolated relay module5 V coil70Cut the JD-VCC jumper and feed the coil separately for true isolation.
0.96″ SSD1306 OLED display3.3–5 V20Static images burn in — invert or scroll the screen periodically.
MFRC522 13.56 MHz RFID reader + card + fob2.5–3.6 V26Strictly 3.3 V — 5 V on the SPI pins kills the chip.
5 V 3 A regulated SMPS adapter5 V3000Measure the real output — many "3 A" adapters sag below 4.7 V at 2 A.

Summed typical draw is 3306 mA. With a 1.5× design margin the supply should deliver at least 5000 mA continuously at the stated rail voltage.

Software Requirements & Development Environment

Reference toolchain: Arduino IDE 2.3.x with the ESP32 board package 3.x (or PlatformIO on VS Code). Anything newer normally works; anything older may lack the board definitions used here.

  • Install the Arduino IDE 2.3.x (or PlatformIO if you prefer a real editor and dependency locking).
  • Add https://espressif.github.io/arduino-esp32/package_esp32_index.json under File → Preferences → Additional Board Manager URLs, then install esp32 from the Boards Manager.
  • Set the correct port under Tools → Port. On Linux add yourself to the dialout group: sudo usermod -aG dialout $USER and log out and back in.
  • Open the Serial Monitor at 115200 baud — every sketch here logs its state there.
  • Keep File → Preferences → Show verbose output during: compilation switched on while you are debugging build errors.

Required libraries

LibraryWhy it is neededInstall
WiFi (ESP32 core) bundledStation/AP connection management for the ESP32.Bundled with the ESP32 Arduino core
PubSubClient 2.8Lightweight MQTT 3.1.1 client for constrained devices.Library Manager → "PubSubClient" by Nick O'Leary
Adafruit SSD1306 + GFX 2.5.xFramebuffer and text/graphics primitives for the OLED.Library Manager → "Adafruit SSD1306"
MFRC522 1.4.xSPI driver and MIFARE authentication for the RC522 reader.Library Manager → "MFRC522" by GithubCommunity
ModbusMaster 2.0.1Modbus-RTU master framing for RS-485 meters and drives.Library Manager → "ModbusMaster" by Doc Walker
ArduinoJson 7.xZero-allocation JSON serialisation and parsing.Library Manager → "ArduinoJson" by Benoit Blanchon
NTPClient / configTime bundledWall-clock time from an NTP server for timestamping.Bundled (`configTime()` on ESP32)

Block Diagram

The block diagram shows the functional decomposition of the system — what senses, what decides, what acts, and where the data ends up.

EV Charging Controller — system block diagramFunctional block diagram of the EV Charging Controller system. SenseHouse loadspare capacityCP/PPvehicle state/cableAuthoriseRFIDDecideESP32safe currentLoad mgmtspare = max−houseSignal/actControl pilotadvertise limitContactorenergise (handshake)AccountSession meterbillingrightrightnone
EV Charging Controller — system block diagram

Circuit Diagram & Wiring

Every signal line in the build is shown below, followed by a pin-by-pin connection table you can work through with a multimeter in hand.

EV Charging Controller — wiring schematicConnection schematic showing which controller pin drives each peripheral. Sensors / InputsControllerActuators / OutputsESP32 DevKit V1(ESP-WROOM-32)3.3 V logic / 5 V USBControl pilotGPIO 25 / 34CP: advertise limit+ read stateProximity (PP)GPIO 35Cablepresence/ratingWhole-supply CTGPIO 32 (ADC)Total house loadRFID (authorise)GPIO 18/19/23/5Who may chargeContactorGPIO 26Energise (afterhandshake)Session meterGPIO 16/17Energy for billingOLEDGPIO 21/22Status/currentWi-FiDashboard/billing
EV Charging Controller — wiring schematic
PeripheralPeripheral pinController pinSignal
Control pilotPWM/ADCGPIO 25 / 34CP: advertise limit + read state
Proximity (PP)ADCGPIO 35Cable presence/rating
Whole-supply CTAOUTGPIO 32 (ADC)Total house load
RFID (authorise)SPIGPIO 18/19/23/5Who may charge
ContactorINGPIO 26Energise (after handshake)
Session meterUARTGPIO 16/17Energy for billing
OLEDSDA/SCLGPIO 21/22Status/current
Wi-Fion-chipDashboard/billing

Wire one row at a time and tick it off — most "it does not work" reports trace back to a single swapped pair.

Wiring explanation

  • The power stage (contactor, control-pilot/proximity circuitry, protection) is safety-critical and must meet IEC 61851 and be installed by a qualified electrician with proper RCD/RCBO and earthing.
  • The controller advertises a current limit via the control-pilot PWM duty cycle; it never switches the car's current directly — the car regulates to the advertised limit.
  • Measure whole-supply current (CT on the incoming supply) so the controller knows the spare capacity for dynamic management.
  • Only energise the contactor after the correct handshake (vehicle detected, ready) and interlocks.
  • Meter the session energy for billing; add RFID/authorisation for shared use.
An ESP32 development board with the ESP-WROOM-32 module and USB connector
ESP32 module advertising a safe current via the control pilot and metering the session. Photograph sourced from Wikimedia Commons — ESP32 Espressif ESP-WROOM-32 Dev Board.jpg. Reused under the licence stated on that page; please check it before republishing.

System Architecture

Read the stack from the bottom up: physical hardware, the firmware that drives it, the transport that moves data off the device, and the software a human actually looks at.

EV Charging Controller — architecture stackLayered architecture from hardware to user interface. Hardware layerESP32 DevKit V1 (ESP-WROOM-32) · ACS712 hall-effect current sensor (20 A)· MFRC522 13.56 MHz RFID reader + card + fobDriver layerwifi · pubsub · ssd1306 · mfrc522Application logicsampling loop · filtering · thresholds · state machineTransport layerWi-Fi + MQTT → charge-point management/billing · TLS · retry and backoffPresentation layerdashboard · mobile notifications · historical charts
EV Charging Controller — architecture stack

Working Principle

Home and shared EV charging has one dominant constraint — the supply is shared and finite — and the controller's core intelligence is managing that safely without wasting it. An EV is an unusually large, sustained load (often as much as the rest of the house combined), so charging it at full tilt on top of the household's other loads can exceed the wiring's or the breaker's rating. The unintelligent solutions are bad: charge slowly always (wastes hours of capacity you often have) or charge fast and risk tripping/overloading. Dynamic load management resolves this by making the charge current a live variable: the controller measures the whole-supply load, subtracts it from the safe maximum, and charges the car with the remaining capacity — fast when the house is quiet, backing off automatically the instant a big load switches on, and ramping back up when it switches off. The car always charges as fast as is safe at that moment.

The elegant part is how the controller sets the charge rate, via the control pilot of the standard AC charging protocol (IEC 61851 / SAE J1772). The charging cable carries a control-pilot line on which the charging equipment outputs a PWM signal, and — crucially — the duty cycle of that PWM encodes the maximum current the vehicle is permitted to draw (a defined linear relationship, e.g. a certain duty cycle means "you may draw up to N amps"). So the controller never switches the car's heavy current itself; it simply advertises a limit on the pilot, and the vehicle's onboard charger regulates its draw to stay within it. To modulate charging for load management, the controller just changes the pilot duty cycle — a low-power signalling action — and the car follows. This is what makes safe, smooth, dynamic current control possible.

Around that sits the protocol handshake and safety sequence, which the controller must follow to interoperate and to be safe. The control-pilot voltage also encodes state — no vehicle, vehicle connected, vehicle ready to charge (and ventilation-required states) — so the controller detects a plugged-in car, waits for the "ready" state, checks the proximity/cable rating, and only then energises the contactor to deliver power, de-energising when the car finishes or unplugs. It never applies power out of sequence. This handshake, plus the protection that must accompany EV charging (residual-current protection, proper earthing), is why the power stage is genuinely safety-critical and standards-governed.

On the safe-charging base, the controller adds the features a real charge point needs. Per-session metering — measuring the real energy delivered each charge — is what enables fair billing at shared, apartment or workplace points, logged per user/session. Authorisation (RFID or app) controls who may charge. Scheduling can steer charging into cheap tariff windows or toward surplus solar. And status/energy reporting feeds a dashboard. Throughout, the design is emphatically honest about scope and safety: EV charging is high-power, safety-critical equipment governed by standards and electrical codes; the power wiring, contactor and protection must be specified and installed by a qualified electrician, and a homebrew controller — however correct its logic — is an educational realisation of the control system, not a certified charger, and must respect the protocol and every interlock. Within that frame, it demonstrates exactly what smart EV charging is: use the standard pilot to charge as fast as the shared supply safely allows at every moment, follow the protocol and protection to do it safely, and meter it so it can be paid for.

The maths behind it

Dynamic load management

plainDynamic load management
Charge with the spare capacity of the supply:

  I_spare = I_supply_max − I_house
  I_charge = clamp(I_spare, I_min_or_0, I_evse_max)

If I_spare < I_min → pause; else advertise I_charge.
Re-evaluate continuously so charging tracks the house load.

Control-pilot current signalling

plainControl-pilot current signalling
The pilot PWM duty cycle encodes the allowed current
(IEC 61851 / J1772), e.g. for a common range:

  I_allowed (A) ≈ duty(%) × 0.6   (6–51 A region)
  → to allow I amps: duty = I / 0.6 (%)

The car regulates its draw to ≤ I_allowed. The controller
sets duty; it does NOT switch the car current directly.

CP state + session energy

plainCP state + session energy
Pilot voltage encodes state:
  12V none | 9V connected | 6V ready | (3V vent)
  energise contactor only in the ready state.

Session energy for billing:
  E_session = ∫ P dt  (real power over the session)

Program Flowchart

The firmware is a single cooperative loop. Nothing blocks for long, so networking, sensing and the user interface all stay responsive.

EV Charging Controller — firmware flowchartControl flow through the main program loop. Vehicle connected +authorised?Handshake (CP state)IdleIdleHandshake (CP state)Measure house load; computespareAdvertise safe current via CPPWMEnergise contactor; metersessionHouse load changed?Re-advertise current (up/down)ContinueRe-advertise current (up/down)Continue
EV Charging Controller — firmware flowchart

Assembly Instructions

Build on a breadboard first and only commit to solder once the whole system has run for an hour without a fault.

  1. Install the safety-critical power stage (qualified)

    Have a qualified electrician install the contactor, control-pilot/proximity circuitry, and the required protection (RCD/RCBO, earthing) per IEC 61851 and local codes. This is not DIY power work.

  2. Set up load measurement and control

    Measure whole-supply current for dynamic management, and wire the controller to output the control-pilot PWM and read its state, and to drive the contactor after the handshake.

  3. Add metering, authorisation and reporting

    Meter session energy for billing, add RFID/app authorisation, and report status/energy to a dashboard; add scheduling for tariff/solar.

Step-by-Step Implementation Guide

Work through these in order. Each step ends in something you can observe, so a failure is always localised to the step you just finished.

  1. Compute safe current and advertise it

    Compute the spare capacity from the whole-supply load, clamp to the EVSE limit, and set the control-pilot duty cycle to advertise it — re-evaluating continuously.

    cppload-mgmt.ino
    #define I_SUPPLY_MAX 32.0f    // A — the supply/wiring safe limit
    #define I_EVSE_MAX   32.0f    // A — charger/cable limit
    #define I_MIN         6.0f    // A — minimum EV charge current
    
    // Amps the EV may draw = spare capacity, clamped.
    float safeChargeCurrent(float houseCurrentA){
      float spare = I_SUPPLY_MAX - houseCurrentA;
      if (spare < I_MIN) return 0;                 // not enough spare -> pause
      return fminf(spare, I_EVSE_MAX);
    }
    
    // Advertise the current to the car via control-pilot PWM duty.
    void setPilotCurrent(float amps){
      float duty = amps / 0.6f;                     // IEC 61851 6-51 A region
      duty = constrain(duty, 10.0f, 90.0f);         // valid PWM range
      setPilotDuty(duty);                           // car regulates to <= amps
    }
    float spare = I_SUPPLY_MAX - houseCurrentAThe spare capacity is the supply limit minus what the house is already drawing — the amount available to charge with, right now.
    if (spare < I_MIN) return 0;If there is not enough spare for the EV's minimum charge current, charging pauses rather than overloading the supply.
    float duty = amps / 0.6f;The allowed current is encoded into the control-pilot PWM duty cycle per the standard — the low-power signal the car obeys.
    setPilotDuty(duty); // car regulates to <= ampsThe controller advertises the limit; the vehicle's onboard charger regulates its draw to stay within it — the controller never switches the heavy current itself.
  2. Follow the handshake, meter and bill

    Detect the vehicle and ready state via the pilot, energise the contactor only when ready, meter session energy per authorised user, and report — modulating the advertised current continuously with the house load.

Complete Source Code

The listing below is complete and compiles as written — there are no elided sections. Read the annotations under each block before you upload it.

cppev-charging-controller.ino
/* ═══════════════════════════════════════════════════════════════
   EV Charging Controller — ESP32 (control logic; EDUCATIONAL)

   Dynamic load management: charges the EV with the supply's spare
   capacity, advertised via the IEC 61851/J1772 control-pilot PWM, with
   the protocol handshake, per-session metering and authorisation.
   The power stage/protection is safety-critical — professional install.
   ══════════════════════════════════════════════════════════════════ */

#include <WiFi.h>
#include <PubSubClient.h>
#include <PZEM004Tv30.h>

#define PIN_CP_PWM   25     // control-pilot PWM out
#define PIN_CP_ADC   34     // control-pilot state (voltage)
#define PIN_HOUSE_CT 32     // whole-supply current
#define PIN_CONTACTOR 26
#define I_SUPPLY_MAX 32.0f
#define I_EVSE_MAX   32.0f
#define I_MIN         6.0f

PZEM004Tv30 session(Serial2, 16, 17);   // session energy meter
WiFiClient net; PubSubClient mqtt(net);
bool authorised=false, charging=false;

enum CPState { CP_NONE, CP_CONNECTED, CP_READY, CP_UNKNOWN };
CPState readCP(){
  int v = analogRead(PIN_CP_ADC);        // map to CP voltage
  float cp = v/4095.0f*12.0f;            // simplified scaling
  if (cp > 10) return CP_NONE;           // ~12V no vehicle
  if (cp > 7)  return CP_CONNECTED;      // ~9V connected
  if (cp > 4)  return CP_READY;          // ~6V ready to charge
  return CP_UNKNOWN;
}

float houseCurrent(){ return analogRead(PIN_HOUSE_CT)/4095.0f * 63.0f; } // A

float safeChargeCurrent(float house){
  float spare = I_SUPPLY_MAX - house;
  if (spare < I_MIN) return 0;
  return fminf(spare, I_EVSE_MAX);
}
void setPilotCurrent(float amps){
  float duty = constrain(amps/0.6f, 10.0f, 90.0f);
  ledcWrite(0, (int)(duty/100.0f*1023));  // advertise via CP PWM
}

void setup(){
  Serial.begin(115200);
  pinMode(PIN_CONTACTOR, OUTPUT);
  ledcSetup(0, 1000, 10); ledcAttachPin(PIN_CP_PWM, 0);  // 1 kHz CP PWM
  WiFi.begin(WIFI_SSID,WIFI_PASS); mqtt.setServer(MQTT_HOST,1883);
}

void loop(){
  if(!mqtt.connected()&&WiFi.status()==WL_CONNECTED) mqtt.connect("evse-1");
  mqtt.loop();

  CPState st = readCP();
  authorised = checkAuthorisation();       // RFID/app

  if (st == CP_READY && authorised){
    float amps = safeChargeCurrent(houseCurrent());
    setPilotCurrent(amps);                 // advertise safe current
    if (amps >= I_MIN){
      if (!charging){ digitalWrite(PIN_CONTACTOR, HIGH); charging=true; } // energise
    } else if (charging){
      digitalWrite(PIN_CONTACTOR, LOW); charging=false;   // pause (no spare)
    }
  } else {                                  // not ready/authorised
    if (charging){ digitalWrite(PIN_CONTACTOR, LOW); charging=false; }
    setPilotCurrent(0);
  }

  float e = session.energy();               // session energy for billing
  char m[200];
  snprintf(m,sizeof m,
    "{\"state\":%d,\"charging\":%s,\"house_A\":%.1f,"
    "\"charge_A\":%.1f,\"session_kWh\":%.3f}",
    st, charging?"true":"false", houseCurrent(),
    safeChargeCurrent(houseCurrent()), e);
  mqtt.publish("evse/1/status", m);

  delay(1000);
}
CPState readCP()Reads the control-pilot voltage to detect vehicle state — none, connected, ready — so the controller only charges when the car signals it is ready.
float amps = safeChargeCurrent(houseCurrent())The charge current is the supply's spare capacity, so the EV charges as fast as is safe given the rest of the house's load, moment to moment.
setPilotCurrent(amps); // advertise safe currentThe safe limit is advertised via the control-pilot PWM; the car regulates to it, so the controller never switches the heavy current directly.
if (!charging){ digitalWrite(PIN_CONTACTOR, HIGH); charging=true; } // energiseThe contactor is energised only in the ready state with authorisation and adequate spare capacity — the protocol-correct, interlocked sequence.
float e = session.energy(); // session energy for billingPer-session energy is metered for fair billing at shared or workplace charge points.

Configuration & Calibration

Configuration steps

  • Set the supply/wiring and EVSE current limits and the minimum charge current.
  • Configure the control-pilot PWM (current encoding) and state thresholds, and the handshake/interlocks.
  • Configure whole-supply measurement for load management, session metering, authorisation and tariff scheduling.
  • Ensure the power stage/protection is professionally installed per standards.

Calibration procedure

An uncalibrated sensor produces confident, precise, wrong numbers. Do this once per physical unit and record the constants.

  1. Pilot signalling

    Verify the control-pilot PWM duty cycle correctly encodes the advertised current per the standard, and that the vehicle regulates to it.

  2. Load management

    Calibrate the whole-supply current measurement and confirm the charge current tracks spare capacity as house load changes.

  3. Metering

    Verify session energy against a reference for accurate billing.

Network Architecture & Connectivity

EV Charging Controller — network topologyPath taken by telemetry from field node to end user. Edge nodesGatewayCloudClientsEVSE controllerESP32Other baysshared supplyWi-Fi 2.4 GHzRouterto backendMQTT/OCPPCPMS/billingsessions + energyDashboardstatus/energyBillingper session
EV Charging Controller — network topology

Communication protocol

Charge status, current and session energy publish continuously; multiple bays coordinate to share supply capacity (site-wide load management). OCPP integration suits managed charge points.

Topic / endpointDirectionPayload
evse/1/statusnode → backendstate, charge/house current, session kWh
evse/1/sessionnode → billinguser, energy, duration (billing)
evse/site/limitbackend → nodessite capacity allocation

Message contract between the device and the broker.

Cloud platform configuration

A charge-point management/billing backend records sessions and energy, allocates site capacity across bays, and handles authorisation and billing (OCPP for standard integration).

Dashboard setup

Per-bay charge status and current vs house/site load, session energy/cost, and billing per user.

Mobile app integration

Charge-status and session-complete notifications; cost per session.

Security considerations

  • Authenticate authorisation and billing; secure the backend.
  • Keep the protocol handshake and safety interlocks robust; the power stage/protection is professionally installed.
  • Coordinate multi-bay load management so the site limit is never exceeded.

Testing Procedure & Expected Output

Test from the bottom up. Confirm power, then each sensor in isolation, then the integrated loop — the first failing step tells you exactly where to look.

TestWhat you should see
Connect an EV (ready state)Handshake completes; contactor energises; charging starts
Switch on a big house loadAdvertised current drops; car ramps down; no overload
Switch it offAdvertised current rises; car ramps up
House load leaves no spareCharging pauses; resumes when spare returns
Unauthorised userNo charging without authorisation
Complete a sessionSession energy metered for billing

Bench-test checklist. If a row fails, stop and fix it before moving on.

Expected output

The dashboard shows charge state, live charge current vs house load, session energy and cost, and billing per user/session.

jsonevse-status.json
{
  "state": 2,
  "charging": true,
  "house_A": 14.0,
  "charge_A": 18.0,
  "session_kWh": 7.42
}

With the house drawing 14 A of a 32 A supply, the EV is advertised 18 A of spare capacity — charging fast but safe; when a big load starts, the advertised current drops automatically to protect the supply.

The dashboard of a modern car
Dynamic load management backs the charge off when the house load rises and speeds it up when it falls. Photograph sourced from Wikimedia Commons — Car dashboard.jpg. Reused under the licence stated on that page; please check it before republishing.

Troubleshooting: Common Errors & Fixes

Trips the main breaker

Likely cause. No/incorrect load management

Fix. Measure whole-supply load and charge only with spare capacity; verify the supply limit

Car won't charge

Likely cause. Pilot/handshake wrong

Fix. Verify CP PWM/state encoding and the handshake sequence; check proximity/cable

Charge current not obeyed

Likely cause. Wrong pilot current encoding

Fix. Correct the duty-cycle-to-current mapping per the standard

Billing inaccurate

Likely cause. Session meter uncalibrated

Fix. Verify session energy against a reference

Safety concern

Likely cause. DIY power stage/protection

Fix. Professional install of contactor and RCD/RCBO/earthing per standards; this is control logic only

The sketch will not upload — "Failed to connect" or "avrdude: stk500_recv()"

Likely cause. The bootloader is not being reached: wrong port, wrong board, a serial monitor holding the port open, or a USB cable that only carries power.

Fix. Close every serial monitor, confirm Tools → Board and Port, and swap to a known data-capable USB cable. On an ESP32 hold BOOT while the IDE prints "Connecting…", then release. If a peripheral is wired to the UART pins (GPIO 1/3 on ESP32, D0/D1 on Uno) unplug it — it fights the programmer.

The board resets in a loop, or the serial monitor prints "Brownout detector was triggered"

Likely cause. The supply cannot deliver peak current. Wi-Fi transmit bursts, relay coils and servos all pull far more than their average draw.

Fix. Power peripherals from a separate regulated supply with a common ground rather than from the board 5 V pin. Add a 470–1000 µF electrolytic capacitor across the supply near the load, and use a real power adapter rather than a laptop USB port.

Serial monitor shows garbage characters

Likely cause. Baud rate mismatch between Serial.begin() and the monitor, or a floating/shared UART line.

Fix. Set the monitor to 115200 to match the sketch. If it still garbles, the crystal or the USB bridge is being confused by noise — shorten the cable and keep motor wiring away from the USB lead.

An I²C device is not detected

Likely cause. Wrong address, missing pull-ups, swapped SDA/SCL, or a bus too long for the pull-up value.

Fix. Run an I²C scanner sketch first — it should print the device address. Most breakout boards include 4.7 kΩ pull-ups, but if you have chained four of them the parallel resistance is too low; remove the pull-ups from all but one board. Keep the bus under 30 cm at 100 kHz.

Wi-Fi connects but MQTT never does (state -2)

Likely cause. Wrong broker address or port, a firewall in the way, or the broker requiring credentials the sketch is not sending.

Fix. Test from a laptop on the same network first: mosquitto_sub -h <broker> -t "#" -v. If that works, the problem is on the device — check the IP literal, port 1883 (or 8883 for TLS), and that client.setServer() runs before connect(). PubSubClient state codes are documented in its header.

Readings arrive for a while and then stop

Likely cause. The Wi-Fi or MQTT session dropped and the sketch never reconnects, or the broker dropped the client on keep-alive timeout.

Fix. Never assume the link stays up. Check WiFi.status() and client.connected() at the top of every loop and reconnect with exponential backoff. Add a watchdog so a wedged network stack reboots the device instead of going silent.

Performance Optimisation

  • Re-evaluate the safe charge current continuously so it tracks house/site load.
  • Use the control pilot to modulate current smoothly; never switch the car current directly.
  • Meter session energy accurately for billing.
  • Coordinate multiple bays to share site capacity.
  • Replace every delay() with a millis() comparison — blocking delays are the single most common cause of dropped readings.
  • Sample sensors on a fixed cadence and publish on a slower one; you almost never need to transmit at the sampling rate.
  • Move networking into its own FreeRTOS task so a slow DNS lookup cannot stall the control loop.
  • Use uint8_t / uint16_t where the range allows; on an 8-bit AVR a 32-bit add costs four times as much.
  • Batch several samples into one MQTT publish. Radio time, not CPU time, dominates the energy budget.
  • Set the MQTT keep-alive to a value that matches your reporting interval so the broker does not churn reconnections.
  • For battery builds use deep sleep between samples: an ESP32 drops from ~160 mA awake to about 10 µA asleep, which is the difference between days and months of runtime.

Safety Precautions

  • EV charging is high-power, safety-critical and standards-governed — the power stage, contactor and protection (RCD/RCBO, earthing) must be professionally installed per IEC 61851 and local codes.
  • This is an educational build of the control logic, NOT a certified charger; respect the protocol and every interlock.
  • Only energise in the correct handshake state; never apply power out of sequence.
  • Dynamic load management protects the wiring — verify the supply limit and measurement.
  • Mains voltage kills. Anything on the load side of the relay is at 230 V. Do not work on a powered circuit, and never leave exposed mains wiring on a bench where someone could touch it.
  • Keep at least 6 mm of creepage between the mains and low-voltage sides of any board you make, and never route mains tracks under the microcontroller.
  • Have a qualified electrician do the final installation into a consumer unit or wall fitting. In most jurisdictions this is a legal requirement, not a suggestion.
  • Fit an RCD/RCBO upstream and fuse the load appropriately for its rating.
  • Wear eye protection when soldering or cutting, and solder in a ventilated space — rosin flux fumes are a respiratory irritant.
  • Power the circuit through a bench supply with a current limit while you are testing. A 300 mA limit turns a wiring mistake into a beep instead of a dead board.
  • Disconnect power before changing any wiring. Hot-plugging a sensor onto a live bus is the fastest way to lose a controller.

Maintenance

  • Verify pilot signalling, handshake and interlocks periodically.
  • Check the load-management measurement and session-meter calibration.
  • Inspect the power stage/protection (professionally).
  • Update authorisation/billing and site-capacity allocation as needed.
  • Re-check every screw terminal and header after the first week — thermal cycling loosens connections that felt tight on day one.
  • Keep the broker and dashboard containers patched, and rotate device credentials at least once a year.
  • Recalibrate at the interval given in the calibration section, and keep the constants in a text file next to the firmware — not only in flash.
  • Keep a short logbook of firmware versions and what changed. Six months later you will not remember why that constant is 1.083.

Future Improvements & Upgrades

A working v1 is a platform, not a finish line. These are the upgrades that add the most capability for the least rework.

  • Add OCPP for standard charge-point management integration.
  • Add solar-surplus charging (charge from export).
  • Add three-phase and vehicle-to-grid where supported.
  • Add site-wide multi-bay dynamic load balancing.
  • Design a proper PCB. Once the breadboard version has run for a month, moving to a two-layer board removes the intermittent-contact failures that dominate prototype faults.
  • Add over-the-air firmware updates so you never have to physically reach a deployed node again.
  • Add persistent local storage (microSD or the on-chip flash) so a network outage does not create a hole in your data.
  • Move configuration out of the source: a captive-portal setup page or a JSON config file makes the build reusable without a recompile.
  • Add a battery and solar option so the unit survives a power cut and can be sited away from a socket.
  • Write a small test harness that feeds synthetic sensor values through the decision logic, so you can validate thresholds without physically triggering the event.

Frequently Asked Questions

How does it charge fast without tripping the breaker?

Dynamic load management: it measures the whole-house load and charges the EV with the spare capacity, backing off automatically when a big load switches on and ramping up when it stops — always as fast as is safe at that moment.

Does it switch the car's current directly?

No. It advertises a current limit to the car via the control-pilot PWM duty cycle (the IEC 61851/J1772 standard), and the car's onboard charger regulates its draw to stay within it. Modulating charging is a low-power signalling action.

Can I build and install this myself?

The control logic, yes, as an educational build. But EV charging is high-power and safety-critical — the power stage, contactor and protection (RCD/RCBO, earthing) must be specified and installed by a qualified electrician per standards. It is not a certified charger.

How does billing work at a shared point?

It meters the real energy delivered each session and logs it per authorised user, which is what fair billing at apartment or workplace charge points needs.

Can it use cheap tariffs or solar?

Yes — it can schedule charging into cheap tariff windows or prioritise charging from surplus solar, since it controls the charge rate and timing.

References & Learning Resources

These are the primary sources worth reading in full. Manufacturer datasheets always outrank forum posts when the two disagree.

  1. IEC 61851 / SAE J1772 EV charging (control pilot)Reference
  2. EVSE and control-pilot signallingReference
  3. Dynamic load management for EV chargingReference
  4. OCPP charge-point managementReference
  5. EV charging safety (RCD/earthing)Reference