Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OCPP: integration of further functions #255

Merged
merged 22 commits into from
Nov 29, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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/[email protected]
erropix/ESP32 [email protected]
lorol/[email protected]
matth-x/[email protected].2
matth-x/[email protected].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