Skip to content

Commit 38f2fd8

Browse files
authored
Merge pull request #255 from matth-x/master
OCPP: integration of further functions
2 parents b114567 + e76d75d commit 38f2fd8

File tree

7 files changed

+275
-14
lines changed

7 files changed

+275
-14
lines changed

platformio.ini

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ lib_deps =
4242
jeremypoulter/[email protected]
4343
erropix/ESP32 [email protected]
4444
45-
45+
4646
lib_ignore = WebSockets ; ArduinoOcpp: don't compile built-in WS library
4747
extra_scripts = scripts/extra_script.py
4848
debug_flags =
@@ -79,6 +79,7 @@ build_flags =
7979
-D MG_ENABLE_SNTP=1
8080
-D CS_PLATFORM=CS_P_ESP32
8181
-D AO_CUSTOM_WS ; ArduinoOcpp: don't use built-in WS library
82+
-D AO_CUSTOM_DIAGNOSTICS ; ArduinoOcpp: don't do internal logging
8283
#-D ENABLE_DEBUG
8384
#-D ENABLE_DEBUG_MONGOOSE_HTTP_CLIENT
8485
-D RAPI_MAX_COMMANDS=20

src/event_log.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ void EventLog::log(EventType type, EvseState managerState, uint8_t evseState, ui
136136
}
137137
}
138138

139-
void EventLog::enumerate(uint32_t index, std::function<void(String time, EventType type, EvseState managerState, uint8_t evseState, uint32_t evseFlags, uint32_t pilot, double energy, uint32_t elapsed, double temperature, double temperatureMax, uint8_t divertMode)> callback)
139+
void EventLog::enumerate(uint32_t index, std::function<void(String time, EventType type, const String &logEntry, EvseState managerState, uint8_t evseState, uint32_t evseFlags, uint32_t pilot, double energy, uint32_t elapsed, double temperature, double temperatureMax, uint8_t divertMode)> callback)
140140
{
141141
String filename = filenameFromIndex(index);
142142
File eventFile = LittleFS.open(filename);
@@ -169,7 +169,7 @@ void EventLog::enumerate(uint32_t index, std::function<void(String time, EventTy
169169
double temperatureMax = json["tm"];
170170
uint8_t divertMode = json["dm"];
171171

172-
callback(time, type, managerState, evseState, evseFlags, pilot, energy, elapsed, temperature, temperatureMax, divertMode);
172+
callback(time, type, line, managerState, evseState, evseFlags, pilot, energy, elapsed, temperature, temperatureMax, divertMode);
173173
}
174174
}
175175
eventFile.close();

src/event_log.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ class EventLog
8585
}
8686

8787
void log(EventType type, EvseState managerState, uint8_t evseState, uint32_t evseFlags, uint32_t pilot, double energy, uint32_t elapsed, double temperature, double temperatureMax, uint8_t divertMode);
88-
void enumerate(uint32_t index, std::function<void(String time, EventType type, EvseState managerState, uint8_t evseState, uint32_t evseFlags, uint32_t pilot, double energy, uint32_t elapsed, double temperature, double temperatureMax, uint8_t divertMode)> callback);
88+
void enumerate(uint32_t index, std::function<void(String time, EventType type, const String &logEntry, EvseState managerState, uint8_t evseState, uint32_t evseFlags, uint32_t pilot, double energy, uint32_t elapsed, double temperature, double temperatureMax, uint8_t divertMode)> callback);
8989
};
9090

9191

src/main.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ void setup()
134134

135135
input_setup();
136136

137-
ocpp.begin(evse, lcd);
137+
ocpp.begin(evse, lcd, eventLog);
138138

139139
lcd.display(F("OpenEVSE WiFI"), 0, 0, 0, LCD_CLEAR_LINE);
140140
lcd.display(currentfirmware, 0, 1, 5 * 1000, LCD_CLEAR_LINE);

src/ocpp.cpp

Lines changed: 254 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
#include <ArduinoOcpp.h> // Facade for ArduinoOcpp
1111
#include <ArduinoOcpp/SimpleOcppOperationFactory.h> // define behavior for incoming req messages
1212

13+
#include "http_update.h"
14+
1315
#include <ArduinoOcpp/Core/OcppEngine.h>
1416

1517
#include "emonesp.h" //for VOLTAGE_DEFAULT
@@ -28,10 +30,11 @@ ArduinoOcppTask::~ArduinoOcppTask() {
2830
instance = NULL;
2931
}
3032

31-
void ArduinoOcppTask::begin(EvseManager &evse, LcdTask &lcd) {
33+
void ArduinoOcppTask::begin(EvseManager &evse, LcdTask &lcd, EventLog &eventLog) {
3234

3335
this->evse = &evse;
3436
this->lcd = &lcd;
37+
this->eventLog = &eventLog;
3538

3639
initializeArduinoOcpp();
3740
loadEvseBehavior();
@@ -60,7 +63,20 @@ void ArduinoOcppTask::initializeArduinoOcpp() {
6063

6164
OCPP_initialize(ocppSocket, (float) VOLTAGE_DEFAULT, ArduinoOcpp::FilesystemOpt::Use, clockAdapter);
6265

63-
bootNotification("Advanced Series", "OpenEVSE", [this](JsonObject payload) {
66+
initializeDiagnosticsService();
67+
initializeFwService();
68+
69+
DynamicJsonDocument *evseDetailsDoc = new DynamicJsonDocument(JSON_OBJECT_SIZE(6));
70+
JsonObject evseDetails = evseDetailsDoc->to<JsonObject>();
71+
evseDetails["chargePointModel"] = "Advanced Series";
72+
//evseDetails["chargePointSerialNumber"] = "TODO"; //see https://github.com/OpenEVSE/ESP32_WiFi_V4.x/issues/218
73+
evseDetails["chargePointVendor"] = "OpenEVSE";
74+
evseDetails["firmwareVersion"] = evse->getFirmwareVersion();
75+
//evseDetails["meterSerialNumber"] = "TODO";
76+
//evseDetails["meterType"] = "TODO";
77+
//see https://github.com/OpenEVSE/ESP32_WiFi_V4.x/issues/219
78+
79+
bootNotification(evseDetailsDoc, [this](JsonObject payload) { //ArduinoOcpp will delete evseDetailsDoc
6480
LCD_DISPLAY("OCPP connected!");
6581
});
6682

@@ -87,14 +103,21 @@ void ArduinoOcppTask::loadEvseBehavior() {
87103
});
88104

89105
setEnergyActiveImportSampler([this] () {
90-
return (float) evse->getTotalEnergy();
106+
float activeImport = 0.f;
107+
activeImport += (float) evse->getTotalEnergy();
108+
activeImport += (float) evse->getSessionEnergy();
109+
return activeImport;
91110
});
92111

93112
setOnChargingRateLimitChange([this] (float limit) { //limit = maximum charge rate in Watts
94113
charging_limit = limit;
95114
this->updateEvseClaim();
96115
});
97116

117+
setConnectorPluggedSampler([this] () {
118+
return (bool) evse->isConnected();
119+
});
120+
98121
setEvRequestsEnergySampler([this] () {
99122
return (bool) evse->isCharging();
100123
});
@@ -103,12 +126,47 @@ void ArduinoOcppTask::loadEvseBehavior() {
103126
return evse->isActive();
104127
});
105128

129+
/*
130+
* Report failures to central system. Note that the error codes are standardized in OCPP
131+
*/
132+
133+
addConnectorErrorCodeSampler([this] () {
134+
if (evse->getEvseState() == OPENEVSE_STATE_GFI_FAULT ||
135+
evse->getEvseState() == OPENEVSE_STATE_NO_EARTH_GROUND ||
136+
evse->getEvseState() == OPENEVSE_STATE_DIODE_CHECK_FAILED) {
137+
return "GroundFailure";
138+
}
139+
return (const char *) NULL;
140+
});
141+
142+
addConnectorErrorCodeSampler([this] () {
143+
if (evse->getEvseState() == OPENEVSE_STATE_OVER_TEMPERATURE) {
144+
return "HighTemperature";
145+
}
146+
return (const char *) NULL;
147+
});
148+
149+
addConnectorErrorCodeSampler([this] () {
150+
if (evse->getEvseState() == OPENEVSE_STATE_OVER_CURRENT) {
151+
return "OverCurrentFailure";
152+
}
153+
return (const char *) NULL;
154+
});
155+
156+
addConnectorErrorCodeSampler([this] () {
157+
if (evse->getEvseState() == OPENEVSE_STATE_STUCK_RELAY ||
158+
evse->getEvseState() == OPENEVSE_STATE_GFI_SELF_TEST_FAILED) {
159+
return "InternalError";
160+
}
161+
return (const char *) NULL;
162+
});
163+
106164
/*
107165
* CP behavior definition: How will plugging and unplugging the EV start or stop OCPP transactions
108166
*/
109167

110168
onVehicleConnect = [this] () {
111-
if (getTransactionId() < 0) {
169+
if (getTransactionId() < 0 && isAvailable()) {
112170
if (!ocpp_idTag.isEmpty()) {
113171
authorize(ocpp_idTag, [this] (JsonObject payload) {
114172
if (idTagIsAccepted(payload)) {
@@ -170,6 +228,24 @@ void ArduinoOcppTask::loadEvseBehavior() {
170228
this->updateEvseClaim();
171229
});
172230

231+
setOnResetReceiveReq([this] (JsonObject payload) {
232+
const char *type = payload["type"] | "Soft";
233+
if (!strcmp(type, "Hard")) {
234+
resetHard = true;
235+
}
236+
237+
resetTime = millis();
238+
resetTriggered = true;
239+
240+
LCD_DISPLAY("Reboot EVSE");
241+
});
242+
243+
setOnUnlockConnector([] () {
244+
//TODO Send unlock command to peripherals. If successful, return true, otherwise false
245+
//see https://github.com/OpenEVSE/ESP32_WiFi_V4.x/issues/230
246+
return false;
247+
});
248+
173249
updateEvseClaim();
174250
}
175251

@@ -207,6 +283,19 @@ unsigned long ArduinoOcppTask::loop(MicroTasks::WakeReason reason) {
207283
onVehicleDisconnect();
208284
}
209285

286+
if (resetTriggered) {
287+
if (millis() - resetTime >= 10000UL) { //wait for 10 seconds after reset command to send the conf msg
288+
resetTriggered = false; //execute only once
289+
290+
if (resetHard) {
291+
//TODO send reset command to all peripherals
292+
//see https://github.com/OpenEVSE/ESP32_WiFi_V4.x/issues/228
293+
}
294+
295+
restart_system();
296+
}
297+
}
298+
210299
return 0;
211300
}
212301

@@ -282,11 +371,11 @@ String ArduinoOcppTask::getCentralSystemUrl() {
282371
if (url.isEmpty()) {
283372
return url; //return empty String
284373
}
285-
if (!url.endsWith("/")) {
286-
url += '/';
287-
}
288374
String chargeBoxId = ocpp_chargeBoxId;
289375
chargeBoxId.trim();
376+
if (!url.endsWith("/") && !chargeBoxId.isEmpty()) {
377+
url += '/';
378+
}
290379
url += chargeBoxId;
291380

292381
if (MongooseOcppSocketClient::isValidUrl(url.c_str())) {
@@ -318,6 +407,164 @@ void ArduinoOcppTask::reconfigure() {
318407
loadEvseBehavior();
319408
}
320409

410+
void ArduinoOcppTask::initializeDiagnosticsService() {
411+
ArduinoOcpp::DiagnosticsService *diagService = ArduinoOcpp::getDiagnosticsService();
412+
if (diagService) {
413+
diagService->setOnUploadStatusSampler([this] () {
414+
if (diagFailure) {
415+
return ArduinoOcpp::UploadStatus::UploadFailed;
416+
} else if (diagSuccess) {
417+
return ArduinoOcpp::UploadStatus::Uploaded;
418+
} else {
419+
return ArduinoOcpp::UploadStatus::NotUploaded;
420+
}
421+
});
422+
423+
diagService->setOnUpload([this] (String &location, ArduinoOcpp::OcppTimestamp &startTime, ArduinoOcpp::OcppTimestamp &stopTime) {
424+
425+
//reset reported state
426+
diagSuccess = false;
427+
diagFailure = false;
428+
429+
//check if input URL is valid
430+
unsigned int port_i = 0;
431+
struct mg_str scheme, query, fragment;
432+
if (mg_parse_uri(mg_mk_str(location.c_str()), &scheme, NULL, NULL, &port_i, NULL, &query, &fragment)) {
433+
DBUG(F("[ocpp] Diagnostics upload, invalid URL: "));
434+
DBUGLN(location);
435+
diagFailure = true;
436+
return false;
437+
}
438+
439+
if (eventLog == NULL) {
440+
diagFailure = true;
441+
return false;
442+
}
443+
444+
//create file to upload
445+
#define BOUNDARY_STRING "-----------------------------WebKitFormBoundary7MA4YWxkTrZu0gW025636501"
446+
const char *bodyPrefix PROGMEM = BOUNDARY_STRING "\r\n"
447+
"Content-Disposition: form-data; name=\"file\"; filename=\"diagnostics.log\"\r\n"
448+
"Content-Type: application/octet-stream\r\n\r\n";
449+
const char *bodySuffix PROGMEM = "\r\n\r\n" BOUNDARY_STRING "--\r\n";
450+
const char *overflowMsg PROGMEM = "{\"diagnosticsMsg\":\"requested search period exceeds maximum diagnostics upload size\"}";
451+
452+
const size_t MAX_BODY_SIZE = 10000; //limit length of message
453+
String body = String('\0');
454+
body.reserve(MAX_BODY_SIZE);
455+
body += bodyPrefix;
456+
body += "[";
457+
const size_t SUFFIX_RESERVED_AREA = MAX_BODY_SIZE - strlen(bodySuffix) - strlen(overflowMsg) - 2;
458+
459+
bool firstEntry = true;
460+
bool overflow = false;
461+
for (uint32_t i = 0; i <= (eventLog->getMaxIndex() - eventLog->getMinIndex()) && !overflow; i++) {
462+
uint32_t index = eventLog->getMinIndex() + i;
463+
464+
eventLog->enumerate(index, [this, startTime, stopTime, &body, SUFFIX_RESERVED_AREA, &firstEntry, &overflow] (String time, EventType type, const String &logEntry, EvseState managerState, uint8_t evseState, uint32_t evseFlags, uint32_t pilot, double energy, uint32_t elapsed, double temperature, double temperatureMax, uint8_t divertMode) {
465+
if (overflow) return;
466+
ArduinoOcpp::OcppTimestamp timestamp = ArduinoOcpp::OcppTimestamp();
467+
if (!timestamp.setTime(time.c_str())) {
468+
DBUG(F("[ocpp] Diagnostics upload, cannot parse timestamp format: "));
469+
DBUGLN(time);
470+
return;
471+
}
472+
473+
if (timestamp < startTime || timestamp > stopTime) {
474+
return;
475+
}
476+
477+
if (body.length() + logEntry.length() + 10 < SUFFIX_RESERVED_AREA) {
478+
if (firstEntry)
479+
firstEntry = false;
480+
else
481+
body += ",";
482+
483+
body += logEntry;
484+
body += "\n";
485+
} else {
486+
overflow = true;
487+
return;
488+
}
489+
});
490+
}
491+
492+
if (overflow) {
493+
if (!firstEntry)
494+
body += ",\r\n";
495+
body += overflowMsg;
496+
}
497+
498+
body += "]";
499+
500+
body += bodySuffix;
501+
502+
DBUG(F("[ocpp] POST diagnostics file to "));
503+
DBUGLN(location);
504+
505+
MongooseHttpClientRequest *request =
506+
diagClient.beginRequest(location.c_str());
507+
request->setMethod(HTTP_POST);
508+
request->addHeader("Content-Type", "multipart/form-data; boundary=" BOUNDARY_STRING);
509+
request->setContent(body.c_str());
510+
request->onResponse([this] (MongooseHttpClientResponse *response) {
511+
if (response->respCode() == 200) {
512+
diagSuccess = true;
513+
} else {
514+
diagFailure = true;
515+
}
516+
});
517+
request->onClose([this] () {
518+
if (!diagSuccess) {
519+
//triggered onClose before onResponse
520+
diagFailure = true;
521+
}
522+
});
523+
diagClient.send(request);
524+
525+
return true;
526+
});
527+
}
528+
}
529+
530+
void ArduinoOcppTask::initializeFwService() {
531+
ArduinoOcpp::FirmwareService *fwService = ArduinoOcpp::getFirmwareService();
532+
if (fwService) {
533+
fwService->setBuildNumber(evse->getFirmwareVersion());
534+
535+
fwService->setInstallationStatusSampler([this] () {
536+
if (updateFailure) {
537+
return ArduinoOcpp::InstallationStatus::InstallationFailed;
538+
} else if (updateSuccess) {
539+
return ArduinoOcpp::InstallationStatus::Installed;
540+
} else {
541+
return ArduinoOcpp::InstallationStatus::NotInstalled;
542+
}
543+
});
544+
545+
fwService->setOnInstall([this](String &location) {
546+
547+
DBUGLN(F("[ocpp] Starting installation routine"));
548+
549+
//reset reported state
550+
updateFailure = false;
551+
updateSuccess = false;
552+
553+
return http_update_from_url(location, [] (size_t complete, size_t total) { },
554+
[this] (int status_code) {
555+
//onSuccess
556+
updateSuccess = true;
557+
558+
resetTime = millis();
559+
resetTriggered = true;
560+
}, [this] (int error_code) {
561+
//onFailure
562+
updateFailure = true;
563+
});
564+
});
565+
}
566+
}
567+
321568
bool ArduinoOcppTask::operationIsAccepted(JsonObject payload) {
322569
const char *status = payload["status"] | "Invalid";
323570
return !strcmp(status, "Accepted");

0 commit comments

Comments
 (0)