Files
Eric F d398a6ced2 Add extracted tools: CitrineOS, OpenOCPP, ShapeShifter
- CitrineOS core extracted (CSMS OCPP 2.0.1)
- OpenOCPP extracted (firmware OCPP 1.6J/2.0.1)
- ShapeShifter library installed (pip install -e)
- ShapeShifter specification extracted
- EVerest extracted

TODO updated with progress
2026-06-08 00:38:27 -04:00

251 lines
8.6 KiB
C++

// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
#include "SessionLog.hpp"
#include "everest/logging.hpp"
#include "v2gMessage.hpp"
#include <algorithm>
#include <chrono>
#include <date/date.h>
#include <date/tz.h>
#include <everest/helpers/helpers.hpp>
#include <filesystem>
#include <optional>
#include <utils/date.hpp>
#include <boost/algorithm/string.hpp>
#include <fmt/core.h>
namespace module {
SessionLog session_log;
SessionLog::SessionLog() {
session_active = false;
enabled = false;
xmloutput = true;
}
SessionLog::~SessionLog() {
if (logfile_csv.is_open()) {
logfile_csv.close();
}
}
void SessionLog::setPath(const std::string& path) {
logpath_root = std::filesystem::weakly_canonical(std::filesystem::path(path));
}
void SessionLog::setMqtt(mqtt_publish_ftor const& mqtt_provider) {
mqtt = mqtt_provider;
}
void SessionLog::enable() {
enabled = true;
}
std::optional<std::filesystem::path> SessionLog::startSession(const std::string& suffix_string) {
if (enabled) {
if (session_active) {
stopSession();
}
// create general log directory if it does not exist
if (!std::filesystem::exists(logpath_root)) {
try {
std::filesystem::create_directories(logpath_root);
} catch (std::filesystem::filesystem_error& e) {
EVLOG_error << fmt::format("Cannot create logpath {}: {}", logpath_root.string(), e.what());
return std::nullopt;
}
}
std::string ts = Everest::Date::to_rfc3339(date::utc_clock::now());
auto ts_suffix_string = fmt::format("{}-{}", ts, suffix_string);
auto logpath_with_suffix = std::filesystem::weakly_canonical(logpath_root / ts_suffix_string);
const auto [root_it, suffix_ix] = std::mismatch(logpath_root.begin(), logpath_root.end(),
logpath_with_suffix.begin(), logpath_with_suffix.end());
if (root_it != logpath_root.end()) {
EVLOG_error << fmt::format("Logpath with suffix ({}) is not a subdirectory of logpath root {}",
logpath_with_suffix.string(), logpath_root.string());
return std::nullopt;
}
logpath = logpath_with_suffix;
// create sessionlog directory if it does not exist
if (!std::filesystem::exists(logpath)) {
std::filesystem::create_directories(logpath);
}
// open new file
fn = logpath / "incomplete-eventlog.csv";
fnhtml = logpath / "incomplete-eventlog.html";
fn_complete = logpath / "eventlog.csv";
fnhtml_complete = logpath / "eventlog.html";
try {
logfile_csv.open(fn);
logfile_html.open(fnhtml);
session_active = true;
} catch (const std::ofstream::failure& e) {
EVLOG_error << fmt::format("Cannot open {} of {} for writing", fn.string(), fnhtml.string());
session_active = false;
}
logfile_html << fmt::format("<html><head><title>EVerest log session {}</title>\n",
everest::helpers::escape_html(suffix_string));
logfile_html << "<style>"
".log {"
" font-family: Arial, Helvetica, sans-serif;"
" border-collapse: collapse;"
" width: 100%;"
"}"
".log td, .log th {"
" border: 1px solid #ddd;"
" padding: 8px;"
" vertical-align: top;"
"}"
".log tr.CAR{background-color: #E4E6F2;}"
".log tr.EVSE{background-color: #F2F0E4;}"
".log tr.SYS{background-color: white;}"
".log th {"
" padding-top: 12px;"
" padding-bottom: 12px;"
" text-align: left;"
" vertical-align: top;"
" background-color: #04AA6D;"
" color: white;"
"}"
"</style>";
logfile_html << "</head><body><table class=\"log\">\n";
sys("Session logging started.");
return logpath;
}
return std::nullopt;
}
void SessionLog::stopSession() {
if (enabled) {
sys("Session logging stopped.");
logfile_html << "</table></body></html>\n";
if (logfile_csv.is_open()) {
logfile_csv.close();
}
if (logfile_html.is_open()) {
logfile_html.close();
}
// rename files to indicate they are finished now
try {
std::filesystem::rename(fn, fn_complete);
} catch (const std::filesystem::filesystem_error& fs_err) {
EVLOG_error << "Could not rename " << fn << ": " << fs_err.what();
}
try {
std::filesystem::rename(fnhtml, fnhtml_complete);
} catch (const std::filesystem::filesystem_error& fs_err) {
EVLOG_error << "Could not rename " << fnhtml << ": " << fs_err.what();
}
session_active = false;
}
}
void SessionLog::evse(bool iso15118, const std::string& msg) {
evse(iso15118, msg, "", "", "", "");
}
void SessionLog::car(bool iso15118, const std::string& msg) {
car(iso15118, msg, "", "", "", "");
}
void SessionLog::evse(bool iso15118, const std::string& msg, const std::string& xml, const std::string& xml_hex,
const std::string& xml_base64, const std::string& json_str) {
output(0, iso15118, msg, xml, xml_hex, xml_base64, json_str);
}
void SessionLog::car(bool iso15118, const std::string& msg, const std::string& xml, const std::string& xml_hex,
const std::string& xml_base64, const std::string& json_str) {
output(1, iso15118, msg, xml, xml_hex, xml_base64, json_str);
}
void SessionLog::output(unsigned int typ, bool iso15118, const std::string& msg, const std::string& xml,
const std::string& xml_hex, const std::string& xml_base64, const std::string& json_str) {
if (enabled && session_active) {
std::string ts = Everest::Date::to_rfc3339(date::utc_clock::now());
std::string xml_pretty;
v2g_message v2g;
if (!xml.empty()) {
v2g.from_xml(xml);
xml_pretty = v2g.to_xml();
} else if (!json_str.empty()) {
v2g.from_json(json_str);
xml_pretty = v2g.to_json();
}
// output to EVerest log
std::string log = msg;
std::string origin, target;
if (xmloutput) {
log += xml_pretty;
}
if (typ == 0) {
origin = "EVSE";
target = "CAR";
EVLOG_info << "\033[1;34mEVSE " << (iso15118 ? "ISO" : "IEC") << " " << log << "\033[1;0m";
} else if (typ == 1) {
origin = "CAR";
target = "EVSE";
EVLOG_info << " \033[1;33mCAR " << (iso15118 ? "ISO" : "IEC") << " "
<< log << "\033[1;0m";
} else {
origin = "SYS";
target = "";
EVLOG_info << "SYS " << msg;
}
// output to session log file
logfile_csv << fmt::format("\"{}\",\"{}\",\"{}\",\"{}\"\n", ts, origin, msg, xml_pretty);
logfile_csv.flush();
// output to session html file
logfile_html << fmt::format("<tr class=\"{}\"> <td>{}</td> <td>{}</td> <td><b>{}</b></td><td><b>{}</b></td> "
"<td><pre lang=\"xml\">{}</pre></td> <td><pre lang=\"xml\">{}</pre></td> <td><pre "
"lang=\"xml\">{}</pre></td> </tr>\n",
origin, ts, origin + "&gt;" + target, (typ == 0 || typ == 2 ? msg : ""),
(typ == 1 ? msg : ""), html_encode(xml_pretty), xml_hex, xml_base64);
logfile_html.flush();
// output to api
nlohmann::json data;
data["origin"] = origin;
data["target"] = target;
data["iso15118"] = iso15118;
data["msg"] = msg;
this->mqtt(data);
}
}
void SessionLog::xmlOutput(bool e) {
xmloutput = e;
}
void SessionLog::sys(const std::string& msg) {
output(2, false, msg, "", "", "", "");
}
std::string SessionLog::html_encode(const std::string& msg) {
std::string out = msg;
boost::replace_all(out, "<", "&lt;");
boost::replace_all(out, ">", "&gt;");
return out;
}
} // namespace module