forked from matth-x/MicroOcpp
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.cpp
More file actions
492 lines (417 loc) · 18.1 KB
/
Copy pathmain.cpp
File metadata and controls
492 lines (417 loc) · 18.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
// matth-x/ArduinoOcpp
// Copyright Matthias Akstaller 2019 - 2022
// MIT License
//
//
// IMPORTANT: Please add https://github.com/tzapu/WiFiManager.git to the lib_deps of your project!
//
// This sketch demonstrates a complete OCPP Wi-Fi module of an EVSE. You can flash
// an ESP8266/ESP32 with this program, build it into your EVSE and start charging with
// OCPP-connectivity.
//
// The pin mapping and additional comments can be found in this file. You probably need
// to adapt a few things before deployment.
//
#include <Arduino.h>
#include <WiFiManager.h> //please add to lib_deps: https://github.com/tzapu/WiFiManager.git
#include <ArduinoOcpp.h>
#include <ArduinoOcpp/Core/Configuration.h> //load and save settings of WiFi captive portal
/*
* Pin mapping part I
* Interface to the SECC (Supply Equipment Communication Controller, e.g. the SAE J1772 module)
*/
#define AMPERAGE_PIN 4 //modulated as PWM
#define EV_PLUG_PIN 14 // Input pin | Read if an EV is connected to the EVSE
#if defined(ESP32)
#define EV_PLUGGED HIGH
#define EV_UNPLUGGED LOW
#elif defined(ESP8266)
#define EV_PLUGGED LOW
#define EV_UNPLUGGED HIGH
#endif
#define OCPP_CHARGE_PERMISSION_PIN 5 // Output pin | Signal if OCPP allows / forbids energy flow
#define OCPP_CHARGE_PERMITTED HIGH
#define OCPP_CHARGE_FORBIDDEN LOW
#define EV_CHARGE_PIN 12 // Input pin | Read if EV requests energy (corresponds to SAE J1772 State C)
#if defined(ESP32)
#define EV_CHARGING LOW
#define EV_SUSPENDED HIGH
#elif defined(ESP8266)
#define EV_CHARGING HIGH
#define EV_SUSPENDED LOW
#endif
#define OCPP_AVAILABILITY_PIN 0 // Output pin | Signal if this EVSE is out of order (set by Central System)
#define OCPP_AVAILABLE HIGH
#define OCPP_UNAVAILABLE LOW
#define EVSE_GROUND_FAULT_PIN 13 // Input pin | Read ground fault detector
#define EVSE_GROUND_FAULTED HIGH
#define EVSE_GROUND_CLEAR LOW
/*
* Pin mapping part II
* Internal LED of ESP8266/ESP32 + additional ESP8266 development board LED
*/
#define SERVER_CONNECT_LED 16 // Output pin | Signal if connection to OCPP server has succeeded
#define SERVER_CONNECT_ON LOW
#define SERVER_CONNECT_OFF HIGH
#define CHARGE_PERMISSION_LED 2 // Output pin | Signal if OCPP allows / forbids energy flow
#if defined(ESP32)
#define CHARGE_PERMISSION_ON HIGH
#define CHARGE_PERMISSION_OFF LOW
#elif defined(ESP8266)
#define CHARGE_PERMISSION_ON LOW
#define CHARGE_PERMISSION_OFF HIGH
#endif
#if !defined(SECC_NO_DEBUG)
#define PRINT(...) Serial.print(__VA_ARGS__)
#define PRINTF(...) Serial.printf(__VA_ARGS__)
#define PRINTLN(...) Serial.println(__VA_ARGS__)
#else
#define PRINT(...)
#define PRINTF(...)
#define PRINTLN(...)
#endif
WebSocketsClient wSock;
ArduinoOcpp::EspWiFi::OcppClientSocket oSock{&wSock};
int evPlugged = EV_UNPLUGGED;
bool booted = false;
ulong scheduleReboot = 0; //0 = no reboot scheduled; otherwise reboot scheduled in X ms
ulong reboot_timestamp = 0; //timestamp of the triggering event; if scheduleReboot=0, the timestamp has no meaning
// ============ CAPTIVE PORTAL
#define CAPTIVE_PORTAL_TIMEOUT 60 //in seconds
#define WIFI_CONNECTION_TIMEOUT 30 //in seconds
struct Ocpp_URL {
bool isTLS = true;
String host = String('\0');
uint16_t port = 443;
String url = String('\0');
bool parse(String& url);
};
Ocpp_URL ocppUrlParsed = Ocpp_URL();
bool runWiFiManager();
// ============ END CAPTIVE PORTAL
void setup() {
/*
* Initialize peripherals
*/
Serial.begin(115200);
#if !defined(SECC_NO_DEBUG)
Serial.setDebugOutput(true);
#endif
pinMode(EV_PLUG_PIN, INPUT);
pinMode(EV_CHARGE_PIN, INPUT);
pinMode(EVSE_GROUND_FAULT_PIN, INPUT);
pinMode(OCPP_CHARGE_PERMISSION_PIN, OUTPUT);
digitalWrite(OCPP_CHARGE_PERMISSION_PIN, OCPP_CHARGE_FORBIDDEN);
pinMode(OCPP_AVAILABILITY_PIN, OUTPUT);
digitalWrite(OCPP_AVAILABILITY_PIN, OCPP_UNAVAILABLE);
pinMode(CHARGE_PERMISSION_LED, OUTPUT);
digitalWrite(CHARGE_PERMISSION_LED, CHARGE_PERMISSION_OFF);
pinMode(AMPERAGE_PIN, OUTPUT);
#if defined(ESP32)
pinMode(AMPERAGE_PIN, OUTPUT);
ledcSetup(0, 1000, 8); //channel=0, freq=1000Hz, range=(2^8)-1
ledcAttachPin(AMPERAGE_PIN, 0);
ledcWrite(AMPERAGE_PIN, 256); //256 is constant +3.3V DC
#elif defined(ESP8266)
analogWriteRange(255); //range=(2^8)-1
analogWriteFreq(1000); //freq=1000Hz
analogWrite(AMPERAGE_PIN, 256); //256 is constant +3.3V DC
#else
#error Can only run on ESP8266 and ESP32 on the Arduino platform!
#endif
pinMode(SERVER_CONNECT_LED, OUTPUT);
digitalWrite(SERVER_CONNECT_LED, SERVER_CONNECT_ON); //signal device reboot
delay(100);
digitalWrite(SERVER_CONNECT_LED, SERVER_CONNECT_OFF);
/*
* You can use ArduinoOcpp's internal configurations store for credentials other than those which are
* specified by OCPP. To use it before ArduinoOcpp is initialized, you need to call configuration_init()
*
* This snippet also shows how to integrate a custom filesystem. Just subclass FilesystemAdapter and pass
* it to the library
*/
std::shared_ptr<ArduinoOcpp::FilesystemAdapter> filesystem = ArduinoOcpp::makeDefaultFilesystemAdapter(ArduinoOcpp::FilesystemOpt::Use_Mount_FormatOnFail);
ArduinoOcpp::configuration_init(filesystem);
/*
* WiFiManager opens a captive portal, lets the user enter the WiFi credentials and provides a settings
* page for the OCPP connection
*/
if (!runWiFiManager()) {
//couldn't connect
PRINTLN(F("[main] Couldn't connect to WiFi after multiple attempts"));
delay(30000);
ESP.restart();
}
std::shared_ptr<ArduinoOcpp::Configuration<const char *>> ocppUrl = ArduinoOcpp::declareConfiguration<const char *>(
"ocppUrl", "", CONFIGURATION_FN, false, false, true, true);
std::shared_ptr<ArduinoOcpp::Configuration<const char *>> CA_cert = ArduinoOcpp::declareConfiguration<const char *>(
"CA_cert", "", CONFIGURATION_FN, false, false, true, true);
std::shared_ptr<ArduinoOcpp::Configuration<const char *>> httpAuthentication = ArduinoOcpp::declareConfiguration<const char *>(
"httpAuthentication", "", CONFIGURATION_FN, false, false, true, true);
String ocppUrlString = String(*ocppUrl);
if (!ocppUrlParsed.parse(ocppUrlString)) {
PRINTLN(F("[main] Invalid OCPP URL. Restart."));
delay(10000);
ESP.restart();
}
PRINTF("[main] host, port, URL: %s, %hu, %s\n",
ocppUrlParsed.host.isEmpty() ? "undefined" : ocppUrlParsed.host.c_str(),
ocppUrlParsed.port,
ocppUrlParsed.url.isEmpty() ? "undefined" : ocppUrlParsed.url.c_str());
/*
* Initialize ArduinoOcpp framework.
*/
wSock.setReconnectInterval(5000);
wSock.enableHeartbeat(15000, 3000, 2);
if (httpAuthentication->getBuffsize() > 1) {
wSock.setAuthorization(*httpAuthentication);
}
if (ocppUrlParsed.isTLS) {
if (CA_cert->getBuffsize() > 0) {
//TODOS: ESP8266: limit BearSSL buffsize
configTime(3 * 3600, 0, "pool.ntp.org", "time.nist.gov"); //alternatively: settimeofday(&de, &tz);
PRINT(F("[main] Wait for NTP time (used for certificate validation) "));
time_t now = time(nullptr);
while (now < 8 * 3600 * 2) {
delay(500);
PRINT('.');
now = time(nullptr);
}
PRINTF(" finished. Unix timestamp is %lu\n", now);
wSock.beginSslWithCA(ocppUrlParsed.host.c_str(), ocppUrlParsed.port, ocppUrlParsed.url.c_str(), *CA_cert, "ocpp1.6");
} else {
wSock.beginSSL(ocppUrlParsed.host.c_str(), ocppUrlParsed.port, ocppUrlParsed.url.c_str(), nullptr, "ocpp1.6");
}
} else {
wSock.begin(ocppUrlParsed.host, ocppUrlParsed.port, ocppUrlParsed.url, "ocpp1.6");
}
OCPP_initialize(oSock,
230.f, //European grid voltage
ArduinoOcpp::FilesystemOpt::Use_Mount_FormatOnFail);
/*
* Integrate OCPP functionality. You can leave out the following part if your EVSE doesn't need it.
*/
setEnergyMeterInput([]() {
//read the energy input register of the EVSE here and return the value in Wh
/*
* Approximated value. Replace with real reading
*/
static ulong lastSampled = millis();
static float energyMeter = 0.f;
if (getTransactionId() > 0)
energyMeter += ((float) (millis() - lastSampled)) * 0.003f; //increase by 0.003Wh per ms (~ 10.8kWh per h)
lastSampled = millis();
return energyMeter;
});
setSmartChargingOutput([](float limit) {
//set the SAE J1772 Control Pilot value here
const float voltage = 230.f; // European grid
const uint nPhases = 1; //one, two or three phase charging
float amps = limit / (voltage * (float) nPhases);
if (amps > 51.f)
amps = 51.f;
PRINTF("[main] Smart Charging allows maximum charge rate: %iW; convert to Control Pilot amerage: %.2fA\n", (int) limit, amps);
int pwmVal;
if (amps < 6.f) {
pwmVal = 256; // = constant +3.3V DC
} else {
pwmVal = (int) (4.26667f * amps);
}
#if defined(ESP32)
ledcWrite(AMPERAGE_PIN, pwmVal);
#elif defined(ESP8266)
analogWrite(AMPERAGE_PIN, pwmVal);
#endif
});
setEvReadyInput([]() {
//return true if the EV is in state "Ready for charging" (see https://en.wikipedia.org/wiki/SAE_J1772#Control_Pilot)
return digitalRead(EV_CHARGE_PIN) == EV_CHARGING;
});
addErrorCodeInput([] () {
//Uncomment if Ground fault pin is used
//if (digitalRead(EVSE_GROUND_FAULT_PIN) != EVSE_GROUND_CLEAR) {
// return "GroundFault";
//}
return (const char *) nullptr;
});
setOnResetSendConf([] (JsonObject confirmation) {
if (getTransactionId() >= 0)
stopTransaction();
PRINTLN(F("[main] Execute reset command"));
reboot_timestamp = millis();
scheduleReboot = 5000; //reboot will be executed in loop()
booted = false;
});
//... see ArduinoOcpp.h for more settings
/*
* Notify the Central System that this station is ready
*/
bootNotification("My Charging Station", "My company name", [] (JsonObject response) {
if (response["status"].as<String>().equals("Accepted")) {
booted = true;
digitalWrite(SERVER_CONNECT_LED, SERVER_CONNECT_ON);
} else {
//Wait for the connection retry
reboot_timestamp = millis();
scheduleReboot = 60000; //wait for 60s until reboot; reboot will be executed in loop()
}
});
}
void loop() {
/*
* Do all OCPP stuff (process WebSocket input, send recorded meter values to Central System, etc.)
*/
OCPP_loop();
//NFC reader integration example
if (/* RFID chip detected? */ false) {
const char *idTag = "my-id-tag"; //e.g. idTag = RFID.readIdTag();
authorize(idTag);
}
if (ocppPermitsCharge()) {
//EVSE is in a charging session and charging is permitted by the OCPP server
digitalWrite(OCPP_CHARGE_PERMISSION_PIN, OCPP_CHARGE_PERMITTED);
digitalWrite(CHARGE_PERMISSION_LED, CHARGE_PERMISSION_ON);
} else {
//Charging is not allowed due to OCPP rules
digitalWrite(OCPP_CHARGE_PERMISSION_PIN, OCPP_CHARGE_FORBIDDEN);
digitalWrite(CHARGE_PERMISSION_LED, CHARGE_PERMISSION_OFF);
}
if (scheduleReboot > 0 && millis() - reboot_timestamp >= scheduleReboot) {
ESP.restart();
}
if (!booted) {
return;
}
auto readEvPlugged = digitalRead(EV_PLUG_PIN);
if (evPlugged == EV_UNPLUGGED && readEvPlugged == EV_PLUGGED //transition from unplugged to plugged
&& getTransactionId() < 0 //no transaction yet
&& isOperative()) { //EVSE is in operative mode
startTransaction("my-id-tag");
} else if (evPlugged == EV_PLUGGED && readEvPlugged == EV_UNPLUGGED //transition from plugged to unplugged
&& getTransactionId() >= 0) { //need to stop transaction
stopTransaction();
}
evPlugged = readEvPlugged;
//... see ArduinoOcpp.h for more possibilities
}
// ### End of the OCPP integration. The following code integrates the
// ### WiFi-Manager into this example sketch.
bool runWiFiManager() {
/*
* Initialize WiFi and start captive portal to set connection credentials
*/
WiFi.mode(WIFI_STA); // explicitly set mode, esp defaults to STA+AP
std::shared_ptr<ArduinoOcpp::Configuration<const char *>> ocppUrl = ArduinoOcpp::declareConfiguration<const char *>(
"ocppUrl", "", CONFIGURATION_FN, false, false, true, true);
std::shared_ptr<ArduinoOcpp::Configuration<const char *>> CA_cert = ArduinoOcpp::declareConfiguration<const char *>(
"CA_cert", "", CONFIGURATION_FN, false, false, true, true);
std::shared_ptr<ArduinoOcpp::Configuration<const char *>> httpAuthentication = ArduinoOcpp::declareConfiguration<const char *>(
"httpAuthentication", "", CONFIGURATION_FN, false, false, true, true);
WiFiManager wifiManager;
wifiManager.setTitle("EVSE configuration portal");
wifiManager.setParamsPage(true);
WiFiManagerParameter ocppUrlParam ("ocppUrl", "OCPP 1.6 Server URL + Charge Box ID", *ocppUrl, 150,"placeholder=\"wss://<domain>:<port>/<path>/<chargeBoxId>\"");
wifiManager.addParameter(&ocppUrlParam);
WiFiManagerParameter divider("<div><br/></div>");
wifiManager.addParameter(÷r);
WiFiManagerParameter caCertParam ("caCert", "CA Certificate", "", 1500,"placeholder=\"Paste here or leave blank\"");
wifiManager.addParameter(&caCertParam);
WiFiManagerParameter caCertSavePararm ("<div><label for='caCertSave' style='width: auto;'>Save new CA Certificate</label><input type='checkbox' name='caCertSave' value='1' style='width: auto; margin-left: 10px; vertical-align: middle;'></div>");
wifiManager.addParameter(&caCertSavePararm);
WiFiManagerParameter httpAuthenticationParam ("httpAuthentication", "HTTP-authentication token", "", 150,"placeholder=\"e.g.: aKu0Q4NjMRC9yO3wRo08\"");
wifiManager.addParameter(&httpAuthenticationParam);
WiFiManagerParameter httpAuthenticationSavePararm ("<div><label for='httpAuthenticationSave' style='width: auto;'>Save new HTTP token</label><input type='checkbox' name='httpAuthenticationSave' value='1' style='width: auto; margin-left: 10px; vertical-align: middle;'></div>");
wifiManager.addParameter(&httpAuthenticationSavePararm);
wifiManager.setSaveParamsCallback([&ocppUrlParam, ocppUrl, &wifiManager, CA_cert, &caCertParam, httpAuthentication, &httpAuthenticationParam] () {
String newOcppUrl = String(ocppUrlParam.getValue());
newOcppUrl.trim();
Ocpp_URL newOcppUrlParsed = Ocpp_URL();
if (newOcppUrlParsed.parse(newOcppUrl)) {
//success
*ocppUrl = newOcppUrl.c_str();
}
bool caCertSave = false;
String caCertChangedParam = String ("caCertSave");
if(wifiManager.server->hasArg(caCertChangedParam)) {
String caCertChanged = String(wifiManager.server->arg(caCertChangedParam));
if (caCertChanged.length() > 0) {
caCertSave = caCertChanged.charAt(0) == '1' || caCertChanged.charAt(0) == 't' || caCertChanged.charAt(0) == 'o';
}
}
if (caCertSave) {
*CA_cert = caCertParam.getValue();
}
bool httpAuthenticationSave = false;
String authChangedParam = String ("httpAuthenticationSave");
if(wifiManager.server->hasArg(authChangedParam)) {
String authChanged = String(wifiManager.server->arg(authChangedParam));
if (authChanged.length() > 0) {
httpAuthenticationSave = authChanged.charAt(0) == '1' || authChanged.charAt(0) == 't' || authChanged.charAt(0) == 'o';
}
}
if (httpAuthenticationSave) {
*httpAuthentication = httpAuthenticationParam.getValue();
}
ArduinoOcpp::configuration_save();
});
wifiManager.setDarkMode(true);
//wifiManager.setConfigPortalTimeout(CAPATITIVE_PORTAL_TIMEOUT / 1000); //if nobody logs in to the portal, continue after timeout
wifiManager.setTimeout(CAPTIVE_PORTAL_TIMEOUT); //if nobody logs in to the portal, continue after timeout
wifiManager.setConnectTimeout(WIFI_CONNECTION_TIMEOUT);
//wifiManager.setSaveConnect(true);
wifiManager.setAPClientCheck(true); // avoid timeout if client connected to softap
PRINTLN(F("[main] Start capatitive portal"));
if (wifiManager.startConfigPortal("EVSE-Config", "evse1234")) {
return true;
} else {
return wifiManager.autoConnect("EVSE-Config", "evse1234");
}
}
bool Ocpp_URL::parse(String &ocppUrl) {
String inputUrl = String(ocppUrl); // wss://host.com:433/path
inputUrl.trim();
if (ocppUrl.isEmpty()) {
return false;
}
url = inputUrl;
inputUrl.toLowerCase();
if (inputUrl.startsWith("wss://")) {
isTLS = true;
port = 443;
} else if (inputUrl.startsWith("ws://")) {
isTLS = false;
port = 80;
} else {
return false;
}
inputUrl = inputUrl.substring(inputUrl.indexOf("://") + strlen("://")); // host.com:433/path
if (inputUrl.isEmpty()) {
return false;
}
host = String('\0');
for (uint i = 0; i < inputUrl.length(); i++) {
if (inputUrl.charAt(i) == ':') { //case host.com:433/path
host = inputUrl.substring(0, i);
uint16_t inputPort = 0;
for (unsigned int j = i + 1; j < inputUrl.length(); j++) {
if (isDigit(inputUrl.charAt(j))) {
inputPort *= (uint16_t) 10;
inputPort += inputUrl.charAt(j) - '0';
} else {
break;
}
}
if (inputPort > 0) {
port = inputPort;
}
break;
} else if (inputUrl.charAt(i) == '/') { //case host.com/path
host = inputUrl.substring(0, i);
break;
}
}
if (host.isEmpty()) {
host = inputUrl;
}
return true;
}