You are on page 1of 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