Skip to content

Commit

Permalink
Merge pull request #255 from matth-x/master
Browse files Browse the repository at this point in the history
OCPP: integration of further functions
  • Loading branch information
glynhudson authored Nov 29, 2021
2 parents b114567 + e76d75d commit 38f2fd8
Show file tree
Hide file tree
Showing 7 changed files with 275 additions and 14 deletions.
3 changes: 2 additions & 1 deletion platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ lib_deps =
jeremypoulter/MicroTasks@0.0.2
erropix/ESP32 AnalogWrite@0.2
lorol/LittleFS_esp32@1.0.5
matth-x/ArduinoOcpp@0.0.2
matth-x/ArduinoOcpp@0.0.4
lib_ignore = WebSockets ; ArduinoOcpp: don't compile built-in WS library
extra_scripts = scripts/extra_script.py
debug_flags =
Expand Down Expand Up @@ -79,6 +79,7 @@ build_flags =
-D MG_ENABLE_SNTP=1
-D CS_PLATFORM=CS_P_ESP32
-D AO_CUSTOM_WS ; ArduinoOcpp: don't use built-in WS library
-D AO_CUSTOM_DIAGNOSTICS ; ArduinoOcpp: don't do internal logging
#-D ENABLE_DEBUG
#-D ENABLE_DEBUG_MONGOOSE_HTTP_CLIENT
-D RAPI_MAX_COMMANDS=20
Expand Down
4 changes: 2 additions & 2 deletions src/event_log.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ void EventLog::log(EventType type, EvseState managerState, uint8_t evseState, ui
}
}

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)
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)
{
String filename = filenameFromIndex(index);
File eventFile = LittleFS.open(filename);
Expand Down Expand Up @@ -169,7 +169,7 @@ void EventLog::enumerate(uint32_t index, std::function<void(String time, EventTy
double temperatureMax = json["tm"];
uint8_t divertMode = json["dm"];

callback(time, type, managerState, evseState, evseFlags, pilot, energy, elapsed, temperature, temperatureMax, divertMode);
callback(time, type, line, managerState, evseState, evseFlags, pilot, energy, elapsed, temperature, temperatureMax, divertMode);
}
}
eventFile.close();
Expand Down
2 changes: 1 addition & 1 deletion src/event_log.h
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ class EventLog
}

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);
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);
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);
};


Expand Down
2 changes: 1 addition & 1 deletion src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ void setup()

input_setup();

ocpp.begin(evse, lcd);
ocpp.begin(evse, lcd, eventLog);

lcd.display(F("OpenEVSE WiFI"), 0, 0, 0, LCD_CLEAR_LINE);
lcd.display(currentfirmware, 0, 1, 5 * 1000, LCD_CLEAR_LINE);
Expand Down
261 changes: 254 additions & 7 deletions src/ocpp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
#include <ArduinoOcpp.h> // Facade for ArduinoOcpp
#include <ArduinoOcpp/SimpleOcppOperationFactory.h> // define behavior for incoming req messages

#include "http_update.h"

#include <ArduinoOcpp/Core/OcppEngine.h>

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

void ArduinoOcppTask::begin(EvseManager &evse, LcdTask &lcd) {
void ArduinoOcppTask::begin(EvseManager &evse, LcdTask &lcd, EventLog &eventLog) {

this->evse = &evse;
this->lcd = &lcd;
this->eventLog = &eventLog;

initializeArduinoOcpp();
loadEvseBehavior();
Expand Down Expand Up @@ -60,7 +63,20 @@ void ArduinoOcppTask::initializeArduinoOcpp() {

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

bootNotification("Advanced Series", "OpenEVSE", [this](JsonObject payload) {
initializeDiagnosticsService();
initializeFwService();

DynamicJsonDocument *evseDetailsDoc = new DynamicJsonDocument(JSON_OBJECT_SIZE(6));
JsonObject evseDetails = evseDetailsDoc->to<JsonObject>();
evseDetails["chargePointModel"] = "Advanced Series";
//evseDetails["chargePointSerialNumber"] = "TODO"; //see https://github.com/OpenEVSE/ESP32_WiFi_V4.x/issues/218
evseDetails["chargePointVendor"] = "OpenEVSE";
evseDetails["firmwareVersion"] = evse->getFirmwareVersion();
//evseDetails["meterSerialNumber"] = "TODO";
//evseDetails["meterType"] = "TODO";
//see https://github.com/OpenEVSE/ESP32_WiFi_V4.x/issues/219

bootNotification(evseDetailsDoc, [this](JsonObject payload) { //ArduinoOcpp will delete evseDetailsDoc
LCD_DISPLAY("OCPP connected!");
});

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

setEnergyActiveImportSampler([this] () {
return (float) evse->getTotalEnergy();
float activeImport = 0.f;
activeImport += (float) evse->getTotalEnergy();
activeImport += (float) evse->getSessionEnergy();
return activeImport;
});

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

setConnectorPluggedSampler([this] () {
return (bool) evse->isConnected();
});

setEvRequestsEnergySampler([this] () {
return (bool) evse->isCharging();
});
Expand All @@ -103,12 +126,47 @@ void ArduinoOcppTask::loadEvseBehavior() {
return evse->isActive();
});

/*
* Report failures to central system. Note that the error codes are standardized in OCPP
*/

addConnectorErrorCodeSampler([this] () {
if (evse->getEvseState() == OPENEVSE_STATE_GFI_FAULT ||
evse->getEvseState() == OPENEVSE_STATE_NO_EARTH_GROUND ||
evse->getEvseState() == OPENEVSE_STATE_DIODE_CHECK_FAILED) {
return "GroundFailure";
}
return (const char *) NULL;
});

addConnectorErrorCodeSampler([this] () {
if (evse->getEvseState() == OPENEVSE_STATE_OVER_TEMPERATURE) {
return "HighTemperature";
}
return (const char *) NULL;
});

addConnectorErrorCodeSampler([this] () {
if (evse->getEvseState() == OPENEVSE_STATE_OVER_CURRENT) {
return "OverCurrentFailure";
}
return (const char *) NULL;
});

addConnectorErrorCodeSampler([this] () {
if (evse->getEvseState() == OPENEVSE_STATE_STUCK_RELAY ||
evse->getEvseState() == OPENEVSE_STATE_GFI_SELF_TEST_FAILED) {
return "InternalError";
}
return (const char *) NULL;
});

/*
* CP behavior definition: How will plugging and unplugging the EV start or stop OCPP transactions
*/

onVehicleConnect = [this] () {
if (getTransactionId() < 0) {
if (getTransactionId() < 0 && isAvailable()) {
if (!ocpp_idTag.isEmpty()) {
authorize(ocpp_idTag, [this] (JsonObject payload) {
if (idTagIsAccepted(payload)) {
Expand Down Expand Up @@ -170,6 +228,24 @@ void ArduinoOcppTask::loadEvseBehavior() {
this->updateEvseClaim();
});

setOnResetReceiveReq([this] (JsonObject payload) {
const char *type = payload["type"] | "Soft";
if (!strcmp(type, "Hard")) {
resetHard = true;
}

resetTime = millis();
resetTriggered = true;

LCD_DISPLAY("Reboot EVSE");
});

setOnUnlockConnector([] () {
//TODO Send unlock command to peripherals. If successful, return true, otherwise false
//see https://github.com/OpenEVSE/ESP32_WiFi_V4.x/issues/230
return false;
});

updateEvseClaim();
}

Expand Down Expand Up @@ -207,6 +283,19 @@ unsigned long ArduinoOcppTask::loop(MicroTasks::WakeReason reason) {
onVehicleDisconnect();
}

if (resetTriggered) {
if (millis() - resetTime >= 10000UL) { //wait for 10 seconds after reset command to send the conf msg
resetTriggered = false; //execute only once

if (resetHard) {
//TODO send reset command to all peripherals
//see https://github.com/OpenEVSE/ESP32_WiFi_V4.x/issues/228
}

restart_system();
}
}

return 0;
}

Expand Down Expand Up @@ -282,11 +371,11 @@ String ArduinoOcppTask::getCentralSystemUrl() {
if (url.isEmpty()) {
return url; //return empty String
}
if (!url.endsWith("/")) {
url += '/';
}
String chargeBoxId = ocpp_chargeBoxId;
chargeBoxId.trim();
if (!url.endsWith("/") && !chargeBoxId.isEmpty()) {
url += '/';
}
url += chargeBoxId;

if (MongooseOcppSocketClient::isValidUrl(url.c_str())) {
Expand Down Expand Up @@ -318,6 +407,164 @@ void ArduinoOcppTask::reconfigure() {
loadEvseBehavior();
}

void ArduinoOcppTask::initializeDiagnosticsService() {
ArduinoOcpp::DiagnosticsService *diagService = ArduinoOcpp::getDiagnosticsService();
if (diagService) {
diagService->setOnUploadStatusSampler([this] () {
if (diagFailure) {
return ArduinoOcpp::UploadStatus::UploadFailed;
} else if (diagSuccess) {
return ArduinoOcpp::UploadStatus::Uploaded;
} else {
return ArduinoOcpp::UploadStatus::NotUploaded;
}
});

diagService->setOnUpload([this] (String &location, ArduinoOcpp::OcppTimestamp &startTime, ArduinoOcpp::OcppTimestamp &stopTime) {

//reset reported state
diagSuccess = false;
diagFailure = false;

//check if input URL is valid
unsigned int port_i = 0;
struct mg_str scheme, query, fragment;
if (mg_parse_uri(mg_mk_str(location.c_str()), &scheme, NULL, NULL, &port_i, NULL, &query, &fragment)) {
DBUG(F("[ocpp] Diagnostics upload, invalid URL: "));
DBUGLN(location);
diagFailure = true;
return false;
}

if (eventLog == NULL) {
diagFailure = true;
return false;
}

//create file to upload
#define BOUNDARY_STRING "-----------------------------WebKitFormBoundary7MA4YWxkTrZu0gW025636501"
const char *bodyPrefix PROGMEM = BOUNDARY_STRING "\r\n"
"Content-Disposition: form-data; name=\"file\"; filename=\"diagnostics.log\"\r\n"
"Content-Type: application/octet-stream\r\n\r\n";
const char *bodySuffix PROGMEM = "\r\n\r\n" BOUNDARY_STRING "--\r\n";
const char *overflowMsg PROGMEM = "{\"diagnosticsMsg\":\"requested search period exceeds maximum diagnostics upload size\"}";

const size_t MAX_BODY_SIZE = 10000; //limit length of message
String body = String('\0');
body.reserve(MAX_BODY_SIZE);
body += bodyPrefix;
body += "[";
const size_t SUFFIX_RESERVED_AREA = MAX_BODY_SIZE - strlen(bodySuffix) - strlen(overflowMsg) - 2;

bool firstEntry = true;
bool overflow = false;
for (uint32_t i = 0; i <= (eventLog->getMaxIndex() - eventLog->getMinIndex()) && !overflow; i++) {
uint32_t index = eventLog->getMinIndex() + i;

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) {
if (overflow) return;
ArduinoOcpp::OcppTimestamp timestamp = ArduinoOcpp::OcppTimestamp();
if (!timestamp.setTime(time.c_str())) {
DBUG(F("[ocpp] Diagnostics upload, cannot parse timestamp format: "));
DBUGLN(time);
return;
}

if (timestamp < startTime || timestamp > stopTime) {
return;
}

if (body.length() + logEntry.length() + 10 < SUFFIX_RESERVED_AREA) {
if (firstEntry)
firstEntry = false;
else
body += ",";

body += logEntry;
body += "\n";
} else {
overflow = true;
return;
}
});
}

if (overflow) {
if (!firstEntry)
body += ",\r\n";
body += overflowMsg;
}

body += "]";

body += bodySuffix;

DBUG(F("[ocpp] POST diagnostics file to "));
DBUGLN(location);

MongooseHttpClientRequest *request =
diagClient.beginRequest(location.c_str());
request->setMethod(HTTP_POST);
request->addHeader("Content-Type", "multipart/form-data; boundary=" BOUNDARY_STRING);
request->setContent(body.c_str());
request->onResponse([this] (MongooseHttpClientResponse *response) {
if (response->respCode() == 200) {
diagSuccess = true;
} else {
diagFailure = true;
}
});
request->onClose([this] () {
if (!diagSuccess) {
//triggered onClose before onResponse
diagFailure = true;
}
});
diagClient.send(request);

return true;
});
}
}

void ArduinoOcppTask::initializeFwService() {
ArduinoOcpp::FirmwareService *fwService = ArduinoOcpp::getFirmwareService();
if (fwService) {
fwService->setBuildNumber(evse->getFirmwareVersion());

fwService->setInstallationStatusSampler([this] () {
if (updateFailure) {
return ArduinoOcpp::InstallationStatus::InstallationFailed;
} else if (updateSuccess) {
return ArduinoOcpp::InstallationStatus::Installed;
} else {
return ArduinoOcpp::InstallationStatus::NotInstalled;
}
});

fwService->setOnInstall([this](String &location) {

DBUGLN(F("[ocpp] Starting installation routine"));

//reset reported state
updateFailure = false;
updateSuccess = false;

return http_update_from_url(location, [] (size_t complete, size_t total) { },
[this] (int status_code) {
//onSuccess
updateSuccess = true;

resetTime = millis();
resetTriggered = true;
}, [this] (int error_code) {
//onFailure
updateFailure = true;
});
});
}
}

bool ArduinoOcppTask::operationIsAccepted(JsonObject payload) {
const char *status = payload["status"] | "Invalid";
return !strcmp(status, "Accepted");
Expand Down
Loading

0 comments on commit 38f2fd8

Please sign in to comment.