Thanks to visit codestin.com
Credit goes to www.scribd.com

0% found this document useful (0 votes)
65 views17 pages

ESP32 EV Charger Code

This document defines constants, variables, and functions for an ESP32-based electric vehicle charger controller that uses an OLED display, DS18B20 temperature sensor, current sensor, and relays. It includes code for WiFi connection, an AsyncWebSocket server on port 80, reading sensor values, setting charging states, and displaying a web UI to control charging settings.

Uploaded by

dkvnsyvkgb
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
65 views17 pages

ESP32 EV Charger Code

This document defines constants, variables, and functions for an ESP32-based electric vehicle charger controller that uses an OLED display, DS18B20 temperature sensor, current sensor, and relays. It includes code for WiFi connection, an AsyncWebSocket server on port 80, reading sensor values, setting charging states, and displaying a web UI to control charging settings.

Uploaded by

dkvnsyvkgb
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
You are on page 1/ 17

#include <SPI.

h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <ESPmDNS.h>
#include <WiFi.h>
#include <ESPAsyncWebServer.h>
#include <Arduino_JSON.h>
//#include <ArduinoJson.h>
#include <OneWire.h>
#include <DallasTemperature.h>

// for 1kHz pwm generation


#define PILOT_GPIO 32
#define CURRENT_SENS_GPIO 35
#define PWM1_Ch 0
#define PWM1_Res 10 // 10 bits = 1024 levels = 0,05859375A per step
#define PWM1_Freq 980 // 980 - 1020 range, 1000 nominal
int PWM1_DutyCycle = 0;

#define SCREEN_WIDTH 128 // OLED display width, in pixels


#define SCREEN_HEIGHT 64 // OLED display height, in pixels

#define RELAY_PIN 25 // charging relay on/off

typedef enum ChargingState {


STATE_A, // Pilot Voltage 12V, Not connected, ADC: 3950
STATE_B, // Pilot Voltage 9V, Connected, ADC: 3408
STATE_C, // Pilot Voltage 6V, Charging, ADC: 2847
STATE_D, // Pilot Voltage 3V, Verntilation r ADC: 2390
STATE_E, // Pilot Voltage 0V, No Power ADC: 1941
STATE_F, // Pilot Voltage -12V, EVSE Error ADC: <1500
STATE_CUSTOM_OFF, // custom state for Home automation, no charging until
STATE_CUSTOM_ON or STATE_A
STATE_CUSTOM_ON
};

ChargingState pilotState = STATE_F; // setting default to error, it will change as


no power is detected at startup

// GPIO where the DS18B20 is connected to


const int oneWireBus = 15;
// Setup a oneWire instance to communicate with any OneWire devices
OneWire oneWire(oneWireBus);
// Pass our oneWire reference to Dallas Temperature sensor
DallasTemperature sensors(&oneWire);
float temperatureC = 0.00;

int chargingCurrent = 14; // current AMPS for charging

// PilotPin circuit is connected to GPIO 34 (Analog ADC1_CH6)


int pilotPin = 34;

//Current sensor values


float ampValue = 0.00;

String message = "";


String amperes1 = "14";
int minvaluePilot = 0;

// variable for storing the pilot value


int pilotValue = 0;
float voltage = 0.00;
String stateStr = "";

// ***************** WIFI *************

const char *ssidArray[] = {"Your_SSID_HERE", "Your_2nd_SSID_HERE" ,


"Your_Telephone_Hot_Spot_SSID" }; //just add to this list or remove as you like...
const char *passwordArray[] = {"YOUR_PASSWORD_HERE", "YOUR_2nd_PASSWORD_HERE" ,
"Your_Telephone_Hot_Spot_Password" };

// to be filled at runtime to found network


const char* ssid = "";
const char* password = "";

// ********* end of known networks ******

// Create AsyncWebServer object on port 80


AsyncWebServer server(80);
AsyncWebSocket ws("/ws"); // access at ws://[esp ip]/ws
AsyncWebSocketClient * globalClient = NULL;

// ************************************

//Json Variable to Hold Slider Values


JSONVar sliderValues, internValues;

//Get Slider Values


String getSliderValues(){
sliderValues["amperes1"] = String(amperes1);
sliderValues["temperatureValue1"] = String(temperatureC);
sliderValues["chargingState"] = String(stateStr);
sliderValues["chargingAmps"] = String(ampValue);

String jsonString = JSON.stringify(sliderValues);


return jsonString;
}

String sendSocketUpdates(){
// We do not want to update slider values periodically
// The slider is updated by user on the webpage
// and other users are notified by getSliderValues() function
// only update generated values like amps that the car is taking
// and internal temperature of the charger
// and the state of the charger
//sliderValues["amperes1"] = String(amperes1);
internValues["temperatureValue1"] = String(temperatureC);
internValues["chargingState"] = String(stateStr);
internValues["chargingAmps"] = String(ampValue);

String jsonString = JSON.stringify(internValues);


return jsonString;
}
// Generally, you should use "unsigned long" for variables that hold time
// The value will quickly become too large for an int to store
unsigned long previousMillis = 0; // will store last time request was
updated
// constants won't change:
const long interval = 4000; // interval at which to send websocket updates to
client (milliseconds)

const char index_html[] PROGMEM = R"rawliteral(


<!DOCTYPE html>
<html>
<head>
<title>Charger Web Server</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
html {font-family: Arial; display: inline-block; text-align: center;}
h2 {font-size: 2.3rem;}
p {font-size: 1.9rem;}
body {max-width: 500px; margin:0px auto; padding-bottom: 20px;}
.slider { -webkit-appearance: none; margin: 14px; width: 380px; height: 25px;
background: #FFD65C;
outline: none; -webkit-transition: .2s; transition: opacity .2s;}
.slider::-webkit-slider-thumb {-webkit-appearance: none; appearance: none; width:
35px; height: 35px; background: #003249; cursor: pointer;}
.slider::-moz-range-thumb { width: 35px; height: 35px; background: #003249;
cursor: pointer; }
</style>
</head>
<body>
<h1>Choose Max Charging Current</h1>

<div class="slidecontainer">
<h2 style="float: left;" >6 </h2> <h2 style="float: right;" > 32</h2>
<input type="range" oninput="updateSlider(this)" onchange="updateSliderAMP(this)"
min="6" max="32" value="14" class="slider" id="ampRange1">
<p>Max charging at: <span id="amperes1"></span> Amp.</p>
<p></p>
<p>Temperature at: <span id="tempValue"></span> &deg;C</p>
<p>Charging State: </p>
<p><span id="chargingText"></span></p>
<p>Car Charges At <span id="chargingAtAmpsText"></span> A Now</p>
<input type="button" id="btnChargeOn" value="Charger OFF" style="float: left;"
onClick="sendOff();">
<input type="button" id="btnChargeOn" value="Charger ON" style="float: right;"
onClick="sendOn();">
</div>

<script>
var gateway = `ws://${window.location.hostname}/ws`;
var websocket;
window.addEventListener('load', onload);
websocket.send("getValues");

function onload(event) {
initWebSocket();
}

function getValues(){
websocket.send("getValues");
}

function sendOff(){
websocket.send("setStateCustomOff");
}

function sendOn(){
websocket.send("setStateCustomOn");
}
function initWebSocket() {
console.log('Trying to open a WebSocket connection…');
websocket = new WebSocket(gateway);
websocket.onopen = onOpen;
websocket.onclose = onClose;
websocket.onmessage = onMessage;
}

function onOpen(event) {
console.log('Connection opened');
getValues();
}

function onClose(event) {
console.log('Connection closed');
setTimeout(initWebSocket, 2000);
}

function updateSlider(element) {
var sliderNumber = element.id.charAt(element.id.length-1);
var sliderValue = document.getElementById(element.id).value;
document.getElementById("amperes"+sliderNumber).innerHTML = sliderValue;
//console.log(sliderValue);
//websocket.send(sliderNumber+"s"+sliderValue.toString());
}
function updateSliderAMP(element) {
var sliderNumber = element.id.charAt(element.id.length-1);
var sliderValue = document.getElementById(element.id).value;
document.getElementById("amperes"+sliderNumber).innerHTML = sliderValue;
console.log(sliderValue);
websocket.send(sliderNumber+"s"+sliderValue.toString());
}

function onMessage(event) {
console.log(event.data);
var myObj = JSON.parse(event.data);
var keys = Object.keys(myObj);

for (var i = 0; i <= (keys.length -1); i++){


var key = keys[i];

if (key == "temperatureValue1") {
document.getElementById(("tempValue").toString()).innerHTML = myObj[key];

}
else if (key == "chargingState") {
document.getElementById(("chargingText").toString()).innerHTML =
myObj[key];
}
else if (key == "chargingAmps") {
document.getElementById(("chargingAtAmpsText").toString()).innerHTML =
myObj[key];
}
else {
document.getElementById(key).innerHTML = myObj[key];
document.getElementById("ampRange"+ (i+1).toString()).value = myObj[key];
}
}
}
</script>

</body>
</html>

)rawliteral";

// Replaces placeholder with button section in your web page


// this code does nothing in charger program, needs to bee cleaned up
String processor(const String& var){
//Serial.println(var);
if(var == "BUTTONPLACEHOLDER"){
String buttons = "";
// buttons += "<h4>Output - GPIO 2</h4><label class=\"switch\"><input
type=\"checkbox\" onchange=\"toggleCheckbox(this)\" id=\"2\" " + outputState(2) +
"><span class=\"slider\"></span></label>";
// buttons += "<h4>Output - GPIO 4</h4><label class=\"switch\"><input
type=\"checkbox\" onchange=\"toggleCheckbox(this)\" id=\"4\" " + outputState(4) +
"><span class=\"slider\"></span></label>";
// buttons += "<h4>Output - GPIO 33</h4><label class=\"switch\"><input
type=\"checkbox\" onchange=\"toggleCheckbox(this)\" id=\"33\" " + outputState(33) +
"><span class=\"slider\"></span></label>";
return buttons;
}
return String();
}

// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)


#define OLED_RESET 27 // Reset pin # (or -1 if sharing Arduino reset pin)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

/*
ESP32-WROOM-32 Pinout
3v3 to opamp _____________________
3.3V--|* *| -- GND
Jtag Reset--|* *| -- GPIO_23
GPIO_36--|* OLED*| -- GPIO_22 <---> OLED SCL
GPIO_39--|* *| -- GPIO__1
GPIO_34--|*Analog IN *| -- GPIO__3
GPIO_35--|*Current sens OLED*| -- GPIO_21 <---> OLED SDA
GPIO_32--|*PWM 1KhZ *| -- NC / GND
GPIO_33--|* *| -- GPIO_19
GPIO_25--|*230V Relay *| -- GPIO_18
GPIO_26--|* *| -- GPIO__5
GPIO_27--|*OLED_RESET *| -- GPIO_17
GPIO_14--|* *| -- GPIO_16
GPIO_12--|* *| -- GPIO__4
GND---|* *| -- GPIO__0
GPIO_13--|* *| -- GPIO__2
GPIO__9--|* Temp Sensor*| -- GPIO_15 <---> Dallas DS18b20 Temperature
sensor
GPIO_10--|* *| -- GPIO__8
GPIO_11--|* *| -- GPIO__7
5.0V--|* EN USB BOOT *| -- GPIO__6
|____**__| |__**______|

Do not use ADC2_ pins to measure when using WiFi


WiFi causes interrupts on those analog_in pins
Use GPIO 32, 33, 34, 35, 36, 39 only for analog measurements

Charging States:
State: Pilot Voltage: EV Resistance: Description: Analog theoretic: (if
pwm is on 1khz)
State A 12V N/A Not Connected 3.177 V = 3943
of 4096 on ADC
State B 9V 2.7K Connected 2.811 V = 3489
of 4096 on ADC
State C 6V 882 Ohm Charging 2.445 V = 3034
of 4096 on ADC
State D 3V 246 Ohm Ventilation Required 2.079 V = 2580
of 4096 on ADC
State E 0V N/A No Power 1.713 V = 2126
of 4096 on ADC
State F -12V N/A EVSE Error 249.198 mV = 309
of 4096 on ADC

To test pikot voltage: take resistor from EV resistance table and put between
pilot-pin and ground
the pilot voltage will drop to 9V with 2.7K resistor etc, then you can measure
the ADC values
It is best to test with 100% pwm = DC 12V from Op-Amp (no square wave), then
simple analogRead();
on PilotPin input will give you the desired ADC value.
Second choice is driving the different voltages from a powersupply through the PP
pin and ground
(bias voltage must be persent on circuit or the ESP pins can be damaged)

measured Analog:
State A: 3.19V and ADC: 3950
State B: 2.75V and ADC: 3408
State C: 2.30V and ADC: 2847
State D: 1.92V and ADC: 2390
State E: 1.57V and ADC: 1941
State F:

Calculation for PWM signal to charge @ x AMPS: (valid for up to 51A)


AMPS = Duty cycle X 0.6 (duty cycle is in %)
Duty Cycle = AMPS / 0.6

example: 6A / 0.6 = 10% PWM


16A / 0.6 = 26.666% PWM
10% PWM * 0.6 = 6A
20% PWM * 0.6 = 12A
51-80A, AMPS = (Duty Cycle - 64)2.5
-----------------------------------------------------------------------------------
----------------------------------------------

(similar to https://ev-olution.yolasite.com/DIY-Arduino-EVSE.php?
fbclid=IwAR23UIyWYvearjFswAKCXX8PWCxq1a4q0NeUoIQT9HDftjr3ByCuptJ1D0w)
(but adapted to 3.3V instead of 5V) => R4=47K, R3=68K, and 5V voltage source is
changed to 3.3V
EV-Ground and ESP32 ground (and psu) must be connected together
https://www.ebay.com/itm/323540774103?hash=item4b54886cd7:g:vd4AAOSwIr9bkKIv at
150mA is sufficient to generate +-12V rails

Op-AMP circuit:

3.3V Pilot to car (PP pin on EV charging plug)


| |
R47K |--|<-| TVS diode-P6KE16A->GND
| |
GND---R68K--|--R200K---|
|
|
____________________
Pilot -- R1(1KOhm) Output1 Pin_1 |*
*| Pin_8 +VCC (+12V)_____________________
10K-10K voltage divider from 3.3V->inverting input(1.75V) Pin_2 |* LF353
*| Pin_7 Output2 (not used) |
From ESP32 GPIO_32 Pin_3 |* OP-AMP
*| Pin_6 Inverting input2 (not used) |
-VCC ------- -12V Pin_4 |*
*| Pin_5 Non inverting input2 (not used) |
| |
____________________| |
|
|
GND---->|0.1uF|---|0.1uF|-
>OP_AMP_pin_8(+12V)__________________________________________________|

Safety-checks are omitted from this version of circuit, proof of concept only if
you are not connected to RCD protected mains
Diode check is not implemented either
-----------------------------------------------------------------------------------
----------------------------------------------
CT- for 32A current sensing on a 2000 turn current clamp, 68 Ohm resistor gives
measuring range to 34A
https://tyler.anairo.com/projects/open-energy-monitor-calculator

circuit is at:
https://learn.openenergymonitor.org/electricity-monitoring/ct-sensors/interface-
with-arduino

Formula for calculating the load in Ampers can be derived from measuring the ADC-
values when using heaters of different wattage.
measure the voltage in the AC with a multimeter
measure the resistance of your loads
calculate the amperage of each load
measure heat-gun or 2000W, 1500W oven then 1000W oven, or even 100-200W load and
plot in excel and get the formula for the fitted line
plug the formula back in a function, you can do both for Amps and kwH

Voltage can be measured like this also for more correct calculation:
https://learn.openenergymonitor.org/electricity-monitoring/voltage-sensing/
measuring-voltage-with-an-acac-power-adapter
-----------------------------------------------------------------------------------
---------------------------------------------
*/

// use tool @ http://javl.github.io/image2cpp/ to get 128x64 bw image formated for


progmem
// 'FluidSensorTechnology', 128x64px

const unsigned char FluidSensorFluidSensorTechnology [] PROGMEM = {


0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff,
0xf9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0x9f,
0xf9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0x9f,
0xf9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0x9f,
0xf9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0x9f,
0xf9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0x9f,
0xf9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0x9f,
0xf9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0x9f,
0xf9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xdf, 0xff, 0xff, 0xdf, 0xdf,
0xff, 0xff, 0x9f,
0xf9, 0xff, 0xfe, 0x0c, 0xf9, 0xce, 0x70, 0x7f, 0x06, 0x04, 0xe7, 0x07, 0x07,
0x81, 0xff, 0x9f,
0xf9, 0xff, 0xfe, 0x7c, 0xfd, 0xce, 0x73, 0xbf, 0x76, 0x7c, 0x67, 0x76, 0x73,
0xbd, 0xff, 0x9f,
0xf9, 0xff, 0xfe, 0x0c, 0xfd, 0xce, 0x73, 0x9f, 0x8e, 0x04, 0x27, 0x0e, 0xfb,
0x89, 0xff, 0x9f,
0xf9, 0xff, 0xfe, 0x7c, 0xfd, 0xce, 0x73, 0x9f, 0xf2, 0x7c, 0x87, 0xe2, 0xfb,
0x83, 0xff, 0x9f,
0xf9, 0xff, 0xfe, 0x7c, 0xfd, 0xce, 0x73, 0xbf, 0x72, 0x7c, 0xc7, 0x72, 0x73,
0xbb, 0xff, 0x9f,
0xf9, 0xff, 0xfe, 0x7c, 0x0c, 0x1e, 0x70, 0x7f, 0x06, 0x04, 0xe7, 0x07, 0x07,
0xb9, 0xff, 0x9f,
0xf9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0x9f,
0xf9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0x9f,
0xf9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0x9f,
0xf9, 0xff, 0xfe, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xf0, 0x03, 0xff, 0xff, 0xff,
0xff, 0xff, 0x9f,
0xf9, 0xff, 0xe0, 0x00, 0x0f, 0xff, 0xff, 0xff, 0x83, 0xf0, 0xff, 0xff, 0xff,
0xff, 0xfb, 0x9f,
0xf9, 0xff, 0x07, 0xff, 0xc1, 0xff, 0xff, 0xfe, 0x1f, 0xfe, 0x3f, 0xff, 0xff,
0xff, 0xf1, 0x9f,
0xf9, 0xfc, 0x3f, 0xff, 0xf8, 0x7f, 0xff, 0xfc, 0x7f, 0xff, 0x8f, 0xff, 0xff,
0xff, 0xc7, 0x9f,
0xf9, 0xf8, 0xff, 0xff, 0xfe, 0x3f, 0xff, 0xf1, 0xff, 0xff, 0xc3, 0xff, 0xff,
0xff, 0x8f, 0x9f,
0xf9, 0xe3, 0xff, 0xff, 0xff, 0x8f, 0xff, 0xe3, 0xff, 0xff, 0xf0, 0xff, 0xff,
0xfe, 0x1f, 0x9f,
0xf9, 0xc7, 0xff, 0xff, 0xff, 0xc7, 0xff, 0x8f, 0xff, 0xff, 0xfc, 0x3f, 0xff,
0xf0, 0x7f, 0x9f,
0xf9, 0x8f, 0xff, 0xff, 0xff, 0xf1, 0xfe, 0x1f, 0xff, 0xff, 0xff, 0x01, 0xff,
0x03, 0xff, 0x9f,
0xf9, 0x9f, 0xfe, 0x00, 0xff, 0xfc, 0x00, 0x7f, 0xf0, 0x03, 0xff, 0xe0, 0x00,
0x1f, 0xff, 0x9f,
0xf9, 0xff, 0xe0, 0x00, 0x0f, 0xff, 0x03, 0xff, 0x81, 0xe0, 0xff, 0xff, 0x87,
0xff, 0xfb, 0x9f,
0xf9, 0xff, 0x07, 0xff, 0xc1, 0xff, 0xff, 0xfe, 0x1f, 0xfc, 0x3f, 0xff, 0xff,
0xff, 0xf1, 0x9f,
0xf9, 0xfc, 0x3f, 0xff, 0xf8, 0x7f, 0xff, 0xfc, 0x7f, 0xff, 0x0f, 0xff, 0xff,
0xff, 0xe3, 0x9f,
0xf9, 0xf8, 0xff, 0xff, 0xfe, 0x3f, 0xff, 0xf1, 0xff, 0xff, 0xc3, 0xff, 0xff,
0xff, 0x8f, 0x9f,
0xf9, 0xe3, 0xff, 0xff, 0xff, 0x8f, 0xff, 0xe3, 0xff, 0xff, 0xf0, 0xff, 0xff,
0xfe, 0x1f, 0x9f,
0xf9, 0xc7, 0xff, 0xff, 0xff, 0xc7, 0xff, 0x8f, 0xff, 0xff, 0xfc, 0x3f, 0xff,
0xf0, 0x7f, 0x9f,
0xf9, 0x8f, 0xff, 0xff, 0xff, 0xf1, 0xfe, 0x1f, 0xff, 0xff, 0xff, 0x03, 0xff,
0x81, 0xff, 0x9f,
0xf9, 0x9f, 0xfe, 0x00, 0xff, 0xf8, 0x00, 0x7f, 0xf0, 0x03, 0xff, 0xe0, 0x00,
0x0f, 0xff, 0x9f,
0xf9, 0xff, 0xe0, 0x00, 0x0f, 0xff, 0x03, 0xff, 0x81, 0xe0, 0xff, 0xff, 0x83,
0xff, 0xff, 0x9f,
0xf9, 0xff, 0x83, 0xff, 0x81, 0xff, 0xff, 0xff, 0x1f, 0xfc, 0x3f, 0xff, 0xff,
0xff, 0xf1, 0x9f,
0xf9, 0xfe, 0x1f, 0xff, 0xf8, 0x7f, 0xff, 0xfc, 0x7f, 0xff, 0x0f, 0xff, 0xff,
0xff, 0xe3, 0x9f,
0xf9, 0xf8, 0xff, 0xff, 0xfe, 0x3f, 0xff, 0xf1, 0xff, 0xff, 0xc3, 0xff, 0xff,
0xff, 0x8f, 0x9f,
0xf9, 0xf1, 0xff, 0xff, 0xff, 0x8f, 0xff, 0xe3, 0xff, 0xff, 0xf0, 0xff, 0xff,
0xfe, 0x1f, 0x9f,
0xf9, 0xc7, 0xff, 0xff, 0xff, 0xc7, 0xff, 0x8f, 0xff, 0xff, 0xfc, 0x3f, 0xff,
0xf8, 0x7f, 0x9f,
0xf9, 0x8f, 0xff, 0xff, 0xff, 0xf1, 0xfe, 0x1f, 0xff, 0xff, 0xff, 0x03, 0xff,
0x81, 0xff, 0x9f,
0xf9, 0x9f, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xe0, 0x00,
0x0f, 0xff, 0x9f,
0xf9, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01,
0xff, 0xff, 0x9f,
0xf9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0x9f,
0xf9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0x9f,
0xf9, 0xff, 0xff, 0x81, 0x81, 0x83, 0xdc, 0xe7, 0x7c, 0x3c, 0xfe, 0x1f, 0x07,
0x7b, 0xff, 0x9f,
0xf9, 0xff, 0xff, 0xe7, 0xbf, 0x39, 0x9c, 0xe3, 0x79, 0x9c, 0xfd, 0xce, 0x73,
0x37, 0xff, 0x9f,
0xf9, 0xff, 0xff, 0xe7, 0x9f, 0x7f, 0x9c, 0xe3, 0x73, 0xcc, 0xf9, 0xe6, 0xff,
0x87, 0xff, 0x9f,
0xf9, 0xff, 0xff, 0xe7, 0x83, 0x7f, 0x80, 0xed, 0x73, 0xcc, 0xf9, 0xe6, 0xe3,
0xcf, 0xff, 0x9f,
0xf9, 0xff, 0xff, 0xe7, 0xbf, 0x39, 0x9c, 0xe4, 0x7b, 0xcc, 0xf9, 0xee, 0x7b,
0xcf, 0xff, 0x9f,
0xf9, 0xff, 0xff, 0xe7, 0x9f, 0x93, 0x9c, 0xe6, 0x79, 0x9c, 0xfc, 0xcf, 0x33,
0xcf, 0xff, 0x9f,
0xf9, 0xff, 0xff, 0xff, 0x83, 0xc7, 0xdf, 0xef, 0x7e, 0x7e, 0x1f, 0x3f, 0x8f,
0xff, 0xff, 0x9f,
0xf9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0x9f,
0xf9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0x9f,
0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xdf,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff
};

float getChargingCurrent(int pin, int noSamples){


int analog = checkAnalog(pin, noSamples);

//float amps = (analog-1900.00)/76.70; // formula from experimental values on


243VAC tested line test on resistive heaters 600-1500W
// using current sensor from sparkfun 30A:
https://www.sparkfun.com/products/11005
// and 68 Ohm burden resistor
double amps = (0.0138 * analog) - 26;
return amps;
}

void scanNet(){
Serial.println("scan start");

// WiFi.scanNetworks will return the number of networks found


int n = WiFi.scanNetworks();
int y = sizeof(ssidArray) / sizeof(ssidArray[0]);
Serial.println("scan done");
if (n == 0) {
Serial.println("no networks found");
} else {
Serial.print(n);
Serial.println(" networks found");
for (int i = 0; i < n; ++i) {
// Print SSID and RSSI for each network found
Serial.print(i + 1);
Serial.print(": ");
Serial.print(WiFi.SSID(i));
Serial.print(" (");
Serial.print(WiFi.RSSI(i));
Serial.print(")");
Serial.println((WiFi.encryptionType(i) == WIFI_AUTH_OPEN)?" ":"*");

for (int x = 0; x < y; x++){


if (WiFi.SSID(i) == ssidArray[x]){ // we found a network
corresponding to a network in our known list
ssid = ssidArray[x];
password = passwordArray[x];
Serial.print("connect to network: ");
Serial.println(ssidArray[x]);
i = n; // the loop work is done, we connect to the first network
and exit...
}
}

delay(10);
}
}
Serial.println("");
}

void initWebSocket() {
ws.onEvent(onEvent);
server.addHandler(&ws);
}
int checkAnalog(int analogPinToTest, int noSamples){ // the op-amp outputs a square
wave for the most part so we find the peak in 500 tries ;)
int maximum = 0;
int minimum = 5000;
int value;
for (int i = 0; i <= noSamples; i++) {
value = analogRead(analogPinToTest); // pilotPin or currentSensPin
if (value <= minimum){
minimum = value;
minvaluePilot = minimum;
}
if (value >= maximum){
maximum = value;
}
}

return maximum;
}

int chargingPWM(int ampsToConvert){


//float pwmsignal = ampsToConvert/0.05859375; // 0.05859375 is 1/1024 of 1A when
using 10bit resolution
float pwmsignal = (ampsToConvert + 3)*17.06667; // is 1/1024 of 1A when using
10bit resolution, My car is for some reason 3A steps wrong on pwm pilot signal and
only starts charging when I ask for 9A and then charges at 6A
return (round(pwmsignal)-1);
}

void onEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType


type, void *arg, uint8_t *data, size_t len) {
switch (type) {
case WS_EVT_CONNECT:
Serial.printf("WebSocket client #%u connected from %s\n", client->id(),
client->remoteIP().toString().c_str());
globalClient = client;
break;
case WS_EVT_DISCONNECT:
Serial.printf("WebSocket client #%u disconnected\n", client->id());
globalClient = NULL;
break;
case WS_EVT_DATA:
handleWebSocketMessage(arg, data, len);
break;
case WS_EVT_PONG:
case WS_EVT_ERROR:
break;
}
}

void handleWebSocketMessage(void *arg, uint8_t *data, size_t len) {


AwsFrameInfo *info = (AwsFrameInfo*)arg;
if (info->final && info->index == 0 && info->len == len && info->opcode ==
WS_TEXT) {
data[len] = 0;
message = (char*)data;
if (message.indexOf("1s") >= 0) {
amperes1 = message.substring(2);
chargingCurrent = amperes1.toInt();
ledcWrite(PWM1_Ch, chargingPWM(chargingCurrent));
Serial.println(chargingCurrent);
Serial.print(getSliderValues());
notifyClients(getSliderValues());
}
/*
if (message.indexOf("2s") >= 0) {
sliderValue2 = message.substring(2);
dutyCycle2 = map(sliderValue2.toInt(), 0, 100, 0, 255);
Serial.println(dutyCycle2);
Serial.print(getSliderValues());
notifyClients(getSliderValues());
}
if (message.indexOf("3s") >= 0) {
sliderValue3 = message.substring(2);
dutyCycle3 = map(sliderValue3.toInt(), 0, 100, 0, 255);
Serial.println(dutyCycle3);
Serial.print(getSliderValues());
notifyClients(getSliderValues());
}
*/
if (strcmp((char*)data, "getValues") == 0) {
notifyClients(getSliderValues());
}
if (strcmp((char*)data, "setStateCustomOff") == 0) { // usage in webpage
javascript: websocket.send("setStateCustomOff");
pilotState=STATE_CUSTOM_OFF;
notifyClients(getSliderValues());
Serial.println("State: STATE_CUSTOM_OFF");
}
if (strcmp((char*)data, "setStateCustomOn") == 0) { // usage in webpage
javascript: websocket.send("setStateCustomOn");
// if car is charging, then we do not want to change the state to anything
else than off
// setting pwm to 1023 in charging mode will result in car to abort charging
and try to start again, and then go to charging error mode
if (!(pilotState == STATE_C)){ // this code will run if car is not charging
pilotState=STATE_CUSTOM_ON;
digitalWrite(RELAY_PIN, LOW); // start with relay off
PWM1_DutyCycle = 1023; // turn pwm to constant on, +12v on pilot so we do
not get EVSE ERROR code (STATE_F)
ledcWrite(PWM1_Ch, PWM1_DutyCycle);
notifyClients(getSliderValues());
Serial.println("State: STATE_CUSTOM_ON");
}
}
}
}

void notifyClients(String sliderValues) {


ws.textAll(sliderValues);
}

void printDisplayData(){
display.clearDisplay();
display.setTextSize(1); // Normal 1:1 pixel scale
display.setTextColor(WHITE); // Draw white text
display.setCursor(0,2);
display.print("SSID: ");
display.println(ssid);
display.println("Http://charger.local");
display.print("charging at ");
display.println(ampValue);
display.print("SSR temp is: ");
display.println(temperatureC);
//display.print("Voltage is: ");
//voltage = (3.3 / 4096) * pilotValue;
//display.println(voltage);
display.print("Setpoint at ");
display.print(chargingCurrent);
display.println(" Amps");
display.println(stateStr); // print status on screen
display.println(WiFi.localIP());
display.display();
//delay(2);
}

void CheckState(ChargingState oldChargingState, int adc_value){


ChargingState newState;
int ampsPWM = chargingPWM(chargingCurrent);
Serial.print("pwm for charging = ");
Serial.println(ampsPWM);

if (adc_value >= 3779 && adc_value < 4096){


newState = STATE_A;

}
else if (adc_value >= 3150 && adc_value < 3779){
newState = STATE_B;
}
else if (adc_value >= 2618 && adc_value < 3150){
newState = STATE_C;
}
else if (adc_value >= 2166 && adc_value < 2618){
newState = STATE_D;
}
else if (adc_value >= 1700 && adc_value < 2166){
newState = STATE_E;
}
else if (adc_value >= 0 && adc_value < 1700){
newState = STATE_F;
}
if (!(oldChargingState == newState)){
pilotState = newState;
switch (pilotState){
case STATE_A:
stateStr = "Not Connected";
digitalWrite(RELAY_PIN, LOW);
PWM1_DutyCycle = 1023; // turn off pwm, constant on
ledcWrite(PWM1_Ch, PWM1_DutyCycle);
//digitalWrite(PILOT_GPIO, HIGH); // set +12V DC on pilot
break;
case STATE_B:
stateStr = "Connected";
//digitalWrite(PILOT_GPIO, LOW); // turn off DC voltage
digitalWrite(RELAY_PIN, LOW);
// Advertize 1kHz square wave and wait until EV goes to charging mode
PWM1_DutyCycle = ampsPWM; // % of 1023 max = Square wave
ledcWrite(PWM1_Ch, PWM1_DutyCycle);
Serial.println(ampsPWM);
delay(20);
break;
case STATE_C:
//PWM1_DutyCycle = 0; // turn off pwm
//ledcWrite(PWM1_Ch, PWM1_DutyCycle);
//delay(100);
// Advertize charging capacity
PWM1_DutyCycle = ampsPWM; // % of 1023 max =14A
ledcWrite(PWM1_Ch, PWM1_DutyCycle);
delay(10); // simulate relay closing time
digitalWrite(RELAY_PIN, HIGH);
stateStr = "Charging";
break;
case STATE_D:
stateStr = "Ventilation Requred";
digitalWrite(RELAY_PIN, LOW); // no charging
// Advertize charging capacity
PWM1_DutyCycle = ampsPWM; // turn off pwm
ledcWrite(PWM1_Ch, PWM1_DutyCycle);
break;
case STATE_E:
stateStr = "No POWER,";
stateStr += '\n';
stateStr += " Ready to connect";
digitalWrite(RELAY_PIN, LOW); // no charging
PWM1_DutyCycle = 1023; // // set +12V DC on pilot
ledcWrite(PWM1_Ch, PWM1_DutyCycle);
break;
case STATE_F:
stateStr = "--- EVSE ERROR ---";
digitalWrite(RELAY_PIN, LOW); // no charging
// Advertize charging capacity
PWM1_DutyCycle = 0; // turn off pwm
ledcWrite(PWM1_Ch, PWM1_DutyCycle);
break;
}
}
printDisplayData();
}

void setup() {
Serial.begin(115200);

// SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally


if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3D)) { // Address 0x3D for 128x64
Serial.println(F("SSD1306 allocation failed"));
for(;;); // Don't proceed, loop forever
}
// OLED reset screen:
/*
pinMode(27, OUTPUT);
digitalWrite(27, LOW);
delay(10);
digitalWrite(27, HIGH);
*/

// Start the DS18B20 sensor


sensors.begin();

// Clear the buffer


display.clearDisplay();
display.println("Bil-lader V_1");
display.display();
delay(500);
display.clearDisplay();

// Display bitmap splash for 2 sec


display.drawBitmap(0, 0, FluidSensorFluidSensorTechnology, 128, 64, WHITE);
display.display();
//delay(2000); // wifi searching takes time
display.clearDisplay();

// Set WiFi to station mode and disconnect from an AP if it was previously


connected
WiFi.mode(WIFI_STA);
WiFi.disconnect();
delay(100);

Serial.println("finding network to connect to...");


scanNet();

WiFi.mode(WIFI_AP);

// *********** WIFI **************


WiFi.begin(ssid, password);

while (WiFi.status() != WL_CONNECTED) {


delay(1000);
Serial.println("Connecting to WiFi..");
}

if(!MDNS.begin("Charger")) {
Serial.println("Error starting mDNS");
return;
}

Serial.println(WiFi.localIP());

initWebSocket();

// Add service to MDNS-SD


MDNS.addService("http", "tcp", 80);

// Route for root / web page


server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
request->send_P(200, "text/html", index_html, processor);
});

server.on("/hello", HTTP_GET, [](AsyncWebServerRequest *request){


request->send(200, "text/plain", "Hello World");
});

// Start TCP (HTTP) server


server.begin();
Serial.println("TCP server started");

//********************************

pinMode(34, INPUT);
pinMode(RELAY_PIN, OUTPUT);
digitalWrite(RELAY_PIN, LOW);
// initialize pwm pin at defined freq and res
ledcAttachPin(PILOT_GPIO, PWM1_Ch);
ledcSetup(PWM1_Ch, PWM1_Freq, PWM1_Res);

void loop() {
// Reading pilot voltage value
pilotValue = checkAnalog(pilotPin, 400); // analog is on square wave, we must
get the maximum (and minimum)

// Only display charging amp value if we are charging, else display 0.00A
if (pilotState == STATE_C){
ampValue = getChargingCurrent(CURRENT_SENS_GPIO, 1000);
} else{
ampValue =0.00;
}
//Serial.println(pilotValue);
//Serial.println(minvaluePilot);
if (pilotState == STATE_CUSTOM_OFF){ // if Home Automation sets charger to off
then we will not change states
stateStr = "Charging on Schedule";
digitalWrite(RELAY_PIN, LOW); // no charging
// Advertize charging capacity
PWM1_DutyCycle = 0; // turn off pwm
ledcWrite(PWM1_Ch, PWM1_DutyCycle);
} else { // we have either not got a custom_off state yet or it has been
released from STATE_CUSTOM_OFF
CheckState(pilotState, pilotValue); // check connection state of charger and do
what the car asks...
}

unsigned long currentMillis = millis();


if (currentMillis - previousMillis >= interval) {
// save the last time you sent message to the webbrowser
previousMillis = currentMillis;
printDisplayData(); // Print status on OLED
sensors.requestTemperatures();
temperatureC = sensors.getTempCByIndex(0);
if(globalClient != NULL && globalClient->status() == WS_CONNECTED){
globalClient->text(sendSocketUpdates()); // send json data
}
}

You might also like