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
This commit is contained in:
Eric F
2026-06-08 00:38:27 -04:00
parent 468cfeaa50
commit d398a6ced2
7326 changed files with 1177561 additions and 7 deletions

View File

@@ -0,0 +1,40 @@
#
# AUTO GENERATED - MARKED REGIONS WILL BE KEPT
# template version 3
#
# module setup:
# - ${MODULE_NAME}: module name
ev_setup_cpp_module()
# ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1
# insert your custom targets and additional config variables here
target_link_libraries(${MODULE_NAME}
PRIVATE
Pal::Sigslot
everest::run_application
everest::system
)
target_sources(${MODULE_NAME}
PRIVATE
"diagnostics_handler.cpp"
"rauc_dbus.cpp"
)
# ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1
target_sources(${MODULE_NAME}
PRIVATE
"main/systemImpl.cpp"
)
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1
# insert other things like install cmds etc here
install(
PROGRAMS
constants.env
diagnostics_uploader.sh
diagnostics_collector.sh
DESTINATION "${EVEREST_MODULE_INSTALL_PREFIX}/${MODULE_NAME}"
)
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1

View File

@@ -0,0 +1,126 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
#include "Linux_Systemd_Rauc.hpp"
#include <exception>
#include <stdexcept>
#include <string_view>
#include <type_traits>
#include <everest/system/safe_system.hpp>
namespace {
template <typename T> void safe_extract(T& dst, const Object& src, const std::string_view& item) {
try {
std::string s{item};
dst = src.at(s);
} catch (const std::exception& ex) {
EVLOG_error << "Store[" << item << "] error: " << ex.what();
if constexpr (std::is_integral_v<T>) {
dst = module::Rauc::request_id_default;
} else {
dst.clear();
}
}
}
} // namespace
namespace module {
void Linux_Systemd_Rauc::init() {
invoke_init(*p_main);
rauc.configure(this->config.VerifyUpdateScriptPath);
store_path = info.id + "_update_transaction";
// This is a transaction we should store permanently
rauc.signal_store_update_transaction.connect([this](Rauc::UpdateTransaction t) {
EVLOG_info << "Store update transaction: " << t.boot_slot << ' ' << t.primary_slot << " id: " << t.request_id;
Object tx = {
{"current_primary_slot", t.primary_slot}, {"current_boot_slot", t.boot_slot}, {"request_id", t.request_id}};
r_store->call_store(store_path, tx);
});
rauc.signal_remove_update_transaction.connect([this]() {
EVLOG_info << "Update transaction removed.";
r_store->call_delete(store_path);
});
rauc.signal_firmware_update_status.connect(
[this](const types::system::FirmwareUpdateStatusEnum& status, int32_t request_id) {
EVLOG_info << "Report status to OCPP: " << types::system::firmware_update_status_enum_to_string(status)
<< " Request id: " << request_id;
p_main->publish_firmware_update_status({status, request_id});
if (status == types::system::FirmwareUpdateStatusEnum::InstallRebooting) {
std::lock_guard<std::recursive_mutex> lock(this->firmware_update_progress_mx);
if (this->firmware_update_waiting_for_ocpp_unblocking) {
EVLOG_info << "Reboot is blocked by OCPP (waiting for 'allow_firmware_installation' call)";
this->firmware_update_reboot_scheduled = true;
} else {
reboot_after_firmware_update();
}
}
if (status == types::system::FirmwareUpdateStatusEnum::InstallVerificationFailed) {
EVLOG_info << "Resetting firmware update state due to reported 'InstallVerificationFailed' status.";
std::unique_lock<std::recursive_mutex> lock(this->firmware_update_progress_mx);
this->firmware_update_waiting_for_ocpp_unblocking = false;
}
});
}
void Linux_Systemd_Rauc::reboot_after_firmware_update() {
{
std::lock_guard<std::recursive_mutex> lock(this->firmware_update_progress_mx);
this->firmware_update_reboot_scheduled = false;
}
EVLOG_error << "-------------- Reboot after installation of update in 10 seconds ---------------";
sleep(10);
try {
auto [cmd, args] = everest::lib::system::split_command_line(config.RebootCommand);
auto res = everest::lib::system::safe_system(cmd, &args);
if (res.status != everest::lib::system::CommandExecutionStatus::CMD_SUCCESS || res.code != 0) {
EVLOG_error << "Unable to trigger reboot: ("
<< everest::lib::system::cmd_execution_status_to_string(res.status) << ": "
<< std::to_string(res.code) << ")";
EVLOG_info << "Failed command: '" << everest::lib::system::command_string_repr(cmd, args) << "'";
}
} catch (const std::exception& ex) {
EVLOG_error << "Configured reboot command is invalid: " << ex.what();
}
}
void Linux_Systemd_Rauc::ready() {
invoke_ready(*p_main);
// Check if we are booting directly after an update install,
// in this case close the update process on the CSMS
if (r_store->call_exists(store_path)) {
Rauc::UpdateTransaction tx;
auto t = std::get<Object>(r_store->call_load(store_path));
safe_extract(tx.boot_slot, t, "current_boot_slot");
safe_extract(tx.primary_slot, t, "current_primary_slot");
safe_extract(tx.request_id, t, "request_id");
rauc.check_previous_transaction(tx);
}
}
void Linux_Systemd_Rauc::firmware_update_may_proceed_with_reboot_callback() {
std::lock_guard<std::recursive_mutex> lock(this->firmware_update_progress_mx);
this->firmware_update_waiting_for_ocpp_unblocking = false;
if (this->firmware_update_reboot_scheduled) {
this->reboot_after_firmware_update();
}
}
void Linux_Systemd_Rauc::install_firmware_bundle(const std::string& filename, int32_t request_id) {
{
std::lock_guard<std::recursive_mutex> lock(this->firmware_update_progress_mx);
this->firmware_update_waiting_for_ocpp_unblocking = true;
}
this->rauc.install_bundle(filename, request_id);
}
} // namespace module

View File

@@ -0,0 +1,83 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
#ifndef LINUX_SYSTEMD_RAUC_HPP
#define LINUX_SYSTEMD_RAUC_HPP
//
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
// template version 2
//
#include "ld-ev.hpp"
// headers for provided interface implementations
#include <generated/interfaces/system/Implementation.hpp>
// headers for required interface implementations
#include <generated/interfaces/kvs/Interface.hpp>
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
// insert your custom include headers here
#include "rauc_dbus.hpp"
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
namespace module {
struct Conf {
double DefaultRetries;
double DefaultRetryInterval;
std::string OCPPLogPath;
std::string SessionLogPath;
std::string RebootCommand;
std::string VerifyUpdateScriptPath;
};
class Linux_Systemd_Rauc : public Everest::ModuleBase {
public:
Linux_Systemd_Rauc() = delete;
Linux_Systemd_Rauc(const ModuleInfo& info, std::unique_ptr<systemImplBase> p_main, std::unique_ptr<kvsIntf> r_store,
Conf& config) :
ModuleBase(info), p_main(std::move(p_main)), r_store(std::move(r_store)), config(config){};
const std::unique_ptr<systemImplBase> p_main;
const std::unique_ptr<kvsIntf> r_store;
const Conf& config;
// ev@1fce4c5e-0ab8-41bb-90f7-14277703d2ac:v1
// insert your public definitions here
Rauc rauc;
void firmware_update_may_proceed_with_reboot_callback();
void install_firmware_bundle(const std::string& filename, int32_t request_id);
// ev@1fce4c5e-0ab8-41bb-90f7-14277703d2ac:v1
protected:
// ev@4714b2ab-a24f-4b95-ab81-36439e1478de:v1
// insert your protected definitions here
// ev@4714b2ab-a24f-4b95-ab81-36439e1478de:v1
private:
friend class LdEverest;
void init();
void ready();
// ev@211cfdbe-f69a-4cd6-a4ec-f8aaa3d1b6c8:v1
// insert your private definitions here
std::string store_path;
std::recursive_mutex firmware_update_progress_mx;
bool firmware_update_waiting_for_ocpp_unblocking =
false; // Set to true in case of OTA update via OCPP; set to false when OCPP has signaled that installation may
// proceed
bool firmware_update_reboot_scheduled = false; // Set to true once a firmware update is installed but a restart has
// been blocked by firmware_update_waiting_for_ocpp_unblocking
void reboot_after_firmware_update();
// ev@211cfdbe-f69a-4cd6-a4ec-f8aaa3d1b6c8:v1
};
// ev@087e516b-124c-48df-94fb-109508c7cda9:v1
// insert other definitions here
// ev@087e516b-124c-48df-94fb-109508c7cda9:v1
} // namespace module
#endif // LINUX_SYSTEMD_RAUC_HPP

View File

@@ -0,0 +1,27 @@
CONNECTION_TIMEOUT=20
DOWNLOADED="Downloaded"
DOWNLOADING="Downloading"
DOWNLOAD_FAILED="DownloadFailed"
DOWNLOAD_SCHEDULED="DownloadScheduled"
DOWNLOAD_PAUSED="DownloadPaused"
IDLE="Idle"
INSTALLATION_FAILED="InstallationFailed"
INSTALLING="Installing"
INSTALLED="Installed"
INSTALL_REBOOTING="InstallRebooting"
INSTALL_SCHEDULED="InstallScheduled"
INSTALL_VERIFICATION_FAILED="InstallVerificationFailed"
PERMISSION_DENIED="PermissionDenied"
NOT_SUPPORTED_OPERATION="NotSupportedOperation"
BAD_MESSAGE="BadMessage"
INVALID_SIGNATURE="InvalidSignature"
SIGNATURE_VERIFIED="SignatureVerified"
UPLOADED="Uploaded"
UPLOAD_FAILURE="UploadFailure"
UPLOADING="Uploading"

View File

@@ -0,0 +1,186 @@
#!/bin/sh
#
#
# SPDX-License-Identifier: Apache-2.0
# Copyright Pionix GmbH and Contributors to EVerest
#
#
# time windowed collection is supported as follows:
# - systemd journal supports "since" and "until" to specify the time window
# - OCPP logs are rotated daily and a log file is included when its modification
# time falls between "since" and "until"
# - Completed session logs are included when their modification time falls
# between "since" and "until" (i.e. the end of session time is used)
# - In-progress session logs are included when their modification time falls
# between "since" and now i.e. where "until" is specified then in-progress
# sessions are unlikely to be included
error() {
echo "$script error: $1" 1>&2
exit 1
}
usage() {
echo "Usage: $script <journal|ocpp|session>"
echo " [--dir /full/path/to/log/directory]"
echo " [--since \"yyyy-mm-dd hh:mm:ss\"]"
echo " [--until \"yyyy-mm-dd hh:mm:ss\"]"
exit 2
}
section_start() {
printf "### START $1\n"
}
section_end() {
printf "\n### END $1\n"
}
# remove T from date/time strings
# 2023-12-18T16:06:03.435Z -> 2023-12-18 16:06:03.435Z
reformat_date() {
pre=$(echo $1 | cut -c1-10)
post=$(echo $1 | cut -c12-19)
if [ -n "$pre" ] && [ -n "$post" ]; then
echo "$pre $post"
fi
}
# obtain the modification time of a file as a timestamp
modify_ts() {
if [ -f "$1" ]; then
stat -c %Y "$1"
else
echo 0
fi
}
# convert the supplied date/time to a timestamp
date_ts() {
if [ -n "$1" ]; then
date -u +%s -d "$1"
fi
}
# output file if modified between optional dates
# $1 file name
# $2 optional not before date
# $3 optional not after date
# $4 line to output before file
output_file() {
if [ -f "$1" ]; then
local modify_time=$(modify_ts "$1")
local not_before=$(date_ts "$2")
local not_after=$(date_ts "$3")
local output=1
[ -n "$not_before" ] && [ $modify_time -lt $not_before ] && output=0
[ -n "$not_after" ] && [ $modify_time -gt $not_after ] && output=0
if [ $output -ne 0 ]; then
[ -n "$4" ] && echo "$4"
sed -e '/^$/d' "$1"
fi
fi
}
# output systemd journal
# $1 optional not before date
# $2 optional not after date
#
# journalctl doesn't like empty arguments
do_journal() {
section_start JOURNAL
if [ -n "$1" ]; then
if [ -n "$2" ]; then
journalctl --output=json --no-pager "--since=$1" "--until=$2"
else
journalctl --output=json --no-pager "--since=$1"
fi
else
journalctl --output=json --no-pager
fi
local res=$?
section_end JOURNAL
return $res
}
# output Everest OCPP logs
# $1 Everest OCPP log directory
# $2 optional not before date
# $3 optional not after date
do_ocpp() {
[ -z "$1" ] && error "Missing OCPP log directory"
local res=0
section_start OCPP
if [ -d "$1" ]; then
find "$1" -name \*.log\* | sort -r | while read ocpp
do
output_file "$ocpp" "$2" "$3"
done
res=$?
fi
section_end OCPP
return $res
}
# output Everest charging session logs
# $1 Everest session log directory
# $2 optional not before date
# $3 optional not after date
do_session() {
[ -z "$1" ] && error "Missing session log directory"
local name
local res=0
section_start SESSION
if [ -d "$1" ]; then
find "$1" -type d | sort -n | while read session
do
name=$(basename "${session}")
if [ -f "${session}/eventlog.csv" ]; then
output_file "${session}/eventlog.csv" "$2" "$3" "--- ${name}"
res=$?
fi
if [ -f "${session}/incomplete-eventlog.csv" ]; then
output_file "${session}/incomplete-eventlog.csv" "$2" "$3" "--- ${name}"
res=$?
fi
done
fi
section_end SESSION
return $res
}
script=$(basename $0)
[ -z "$1" ] && error "Missing sub-command"
cmd=$(basename "${1}")
shift
TEMP=$(getopt -o "" --long 'dir:,since:,until:' -n "$script" -- "$@")
if [ $? -ne 0 ]; then
error "getopt parsing"
fi
# Note the quotes around "$TEMP": they are essential!
eval set -- "$TEMP"
unset TEMP
dir=
since=
until=
while true
do
case "$1" in
"--dir"|"--since"|"--until") eval "${1#--}=\"$2\""; shift 2; continue;;
"--") shift; break;;
esac
done
since=$(reformat_date "$since")
until=$(reformat_date "$until")
case $cmd in
journal) do_journal "$since" "$until";;
ocpp) do_ocpp "$dir" "$since" "$until";;
session) do_session "$dir" "$since" "$until";;
*) usage;;
esac

View File

@@ -0,0 +1,79 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
#include "diagnostics_handler.hpp"
#include <everest/logging.hpp>
#include <everest/system/safe_system.hpp>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <vector>
namespace module {
DiagnosticsHandler::log_result_t DiagnosticsHandler::create_log(const std::string& filename,
const std::optional<std::string>& from,
const std::optional<std::string>& to) {
log_result_t result = log_result_t::success;
int fd;
if ((fd = open(filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC, S_IWUSR | S_IXUSR)) == -1) {
EVLOG_error << "Unable to create file journal file: " << filename << " (" << errno << ")";
result = log_result_t::error_file;
} else {
result = create_journal_log(fd, from, to);
if (result == log_result_t::success) {
result = create_ocpp_log(fd, from, to);
}
if (result == log_result_t::success) {
result = create_session_log(fd, from, to);
}
(void)close(fd);
}
return result;
}
DiagnosticsHandler::log_result_t DiagnosticsHandler::collect_logs(int fd, const char* logType,
const std::optional<std::string>& logDir,
const std::optional<std::string>& from,
const std::optional<std::string>& to) {
log_result_t result = log_result_t::success;
std::string arg_cmd = "diagnostics_collector.sh";
std::vector<std::string> args;
std::string cmd = script_dir + "/" + arg_cmd;
args.push_back(arg_cmd);
args.emplace_back(logType);
if (logDir.has_value()) {
args.emplace_back("--dir");
args.push_back(logDir.value());
}
if (from.has_value()) {
args.emplace_back("--since");
args.push_back(from.value());
}
if (to.has_value()) {
args.emplace_back("--until");
args.push_back(to.value());
}
const auto res = everest::lib::system::safe_system(fd, cmd, &args);
if (res.status != everest::lib::system::CommandExecutionStatus::CMD_SUCCESS || res.code != 0) {
EVLOG_error << "Unable to extract journal logs from:" << from.value_or("<not specified>")
<< " to:" << to.value_or("<not specified>") << " ("
<< everest::lib::system::cmd_execution_status_to_string(res.status) << ": "
<< std::to_string(res.code) << ")";
EVLOG_info << "Failed command: '" << everest::lib::system::command_string_repr(cmd, args) << "'";
result = log_result_t::error_parameter;
}
return result;
}
} // namespace module

View File

@@ -0,0 +1,58 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
#ifndef DIAGNOSTICS_HANDLER_HPP
#define DIAGNOSTICS_HANDLER_HPP
#include <optional>
#include <string>
namespace module {
class DiagnosticsHandler {
protected:
std::string script_dir;
std::string ocpp_dir;
std::string session_dir;
public:
enum class log_result_t {
success,
error,
error_file,
error_parameter,
};
DiagnosticsHandler() = delete;
DiagnosticsHandler(const std::string& scriptDir, const std::string& ocppDir, const std::string& sessionDir) :
script_dir(scriptDir), ocpp_dir(ocppDir), session_dir(sessionDir) {
}
log_result_t create_log(const std::string& filename, const std::optional<std::string>& from,
const std::optional<std::string>& to);
protected:
log_result_t create_journal_log(int fd, const std::optional<std::string>& from,
const std::optional<std::string>& to) {
return collect_logs(fd, "journal", std::nullopt, from, to);
}
log_result_t create_ocpp_log(int fd, const std::optional<std::string>& from, const std::optional<std::string>& to) {
return collect_logs(fd, "ocpp", ocpp_dir, from, to);
}
log_result_t create_session_log(int fd, const std::optional<std::string>& from,
const std::optional<std::string>& to) {
return collect_logs(fd, "session", session_dir, from, to);
}
/// @brief collect logs between two optional dates
/// @param fd - the file descriptor to write the logs to
/// @param logType - journal/ocpp/session
/// @param logDir - directory location for OCPP and session logs
/// @param from - format: "yyyy-mm-dd hh:mm"
/// @param to - format: "yyyy-mm-dd hh:mm"
/// @return result
log_result_t collect_logs(int fd, const char* logType, const std::optional<std::string>& logDir,
const std::optional<std::string>& from, const std::optional<std::string>& to);
};
} // namespace module
#endif // DIAGNOSTICS_HANDLER_HPP

View File

@@ -0,0 +1,27 @@
#!/bin/bash
#
# SPDX-License-Identifier: Apache-2.0
# Copyright Pionix GmbH and Contributors to EVerest
#
. "${1}"
echo "$UPLOADING"
sleep 2
curl -L --progress-bar --connect-timeout "$CONNECTION_TIMEOUT" -T "${4}" "${2}"
curl_exit_code=$?
if [[ $curl_exit_code -eq 0 ]]; then
echo "$UPLOADED"
elif [[ $curl_exit_code -eq 67 ]] || [[ $curl_exit_code -eq 35 ]] || [[ $curl_exit_code -eq 69 ]] ||
[[ $curl_exit_code -eq 9 ]]; then
echo "$PERMISSION_DENIED"
elif [[ $curl_exit_code -eq 3 ]] || [[ $curl_exit_code -eq 6 ]] || [[ $curl_exit_code -eq 10 ]] ||
[[ $curl_exit_code -eq 87 ]]; then
echo "$BAD_MESSAGE"
elif [[ $curl_exit_code -eq 1 ]]; then
echo "$NOT_SUPPORTED_OPERATION"
else
echo "$UPLOAD_FAILURE"
fi

View File

@@ -0,0 +1,191 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
#include "systemImpl.hpp"
#include "diagnostics_handler.hpp"
#include <fstream>
#include <everest/run_application/run_application.hpp>
using namespace everest::run_application;
namespace module {
namespace main {
const std::string CONSTANTS = "constants.env";
const std::string DIAGNOSTICS_UPLOADER = "diagnostics_uploader.sh";
namespace fs = std::filesystem;
// FIXME (aw): this function needs to be refactored into some kind of utility library
fs::path create_temp_file(const fs::path& dir, const std::string& prefix) {
const std::string fn_template = (dir / prefix).string() + "XXXXXX" + std::string(1, '\0');
std::vector<char> fn_template_buffer{fn_template.begin(), fn_template.end()};
// mkstemp needs to have at least 6 XXXXXX at the end and it will replace these
// with a valid file name
auto fd = mkstemp(fn_template_buffer.data());
if (fd == -1) {
EVLOG_AND_THROW(Everest::EverestBaseRuntimeError("Failed to create temporary file at: " + fn_template));
}
// close the file descriptor
close(fd);
return fn_template_buffer.data();
}
void systemImpl::init() {
this->scripts_path = mod->info.paths.libexec;
}
void systemImpl::ready() {
}
types::system::UpdateFirmwareResponse
systemImpl::handle_update_firmware(types::system::FirmwareUpdateRequest& firmware_update_request) {
// FIXME: implement planned updates at a specific time
// FIXME: we don't care about the certificate and signature provided as an argument for now.
// RAUC will not use them anyhow and updates will be equally secure whether they are launched by OCPP secure update
// mechanism or the old non-secure mechanism.
if (mod->rauc.is_idle()) {
EVLOG_info << "Installing bundle from URL: " << firmware_update_request.location;
this->mod->install_firmware_bundle(firmware_update_request.location, firmware_update_request.request_id);
return types::system::UpdateFirmwareResponse::Accepted;
} else {
return types::system::UpdateFirmwareResponse::Rejected;
}
}
types::system::UploadLogsResponse
systemImpl::handle_upload_logs(types::system::UploadLogsRequest& upload_logs_request) {
types::system::UploadLogsResponse response;
if (this->log_upload_running) {
response.upload_logs_status = types::system::UploadLogsStatus::AcceptedCanceled;
} else {
response.upload_logs_status = types::system::UploadLogsStatus::Accepted;
}
const auto date_time = Everest::Date::to_rfc3339(date::utc_clock::now());
const auto diagnostics_file_path = create_temp_file(fs::temp_directory_path(), "diagnostics-" + date_time);
const auto diagnostics_file_name = diagnostics_file_path.filename().string();
response.upload_logs_status = types::system::UploadLogsStatus::Accepted;
response.file_name = diagnostics_file_name;
// populate file with available logs within the specified time window
DiagnosticsHandler diag(mod->info.paths.libexec, mod->config.OCPPLogPath, mod->config.SessionLogPath);
const auto create_result = diag.create_log(diagnostics_file_path.c_str(), upload_logs_request.oldest_timestamp,
upload_logs_request.latest_timestamp);
this->upload_logs_thread =
std::thread([this, create_result, upload_logs_request, diagnostics_file_name, diagnostics_file_path]() {
if (this->log_upload_running) {
EVLOG_info << "Received Log upload request and log upload already running - cancelling current upload";
this->interrupt_log_upload.exchange(true);
EVLOG_info << "Waiting for other log upload to finish...";
std::unique_lock<std::mutex> lk(this->log_upload_mutex);
this->log_upload_cv.wait(lk, [this]() { return !this->log_upload_running; });
EVLOG_info << "Previous Log upload finished!";
}
std::lock_guard<std::mutex> lg(this->log_upload_mutex);
EVLOG_info << "Starting upload of log file";
this->interrupt_log_upload.exchange(false);
this->log_upload_running = true;
const auto diagnostics_uploader = this->scripts_path / DIAGNOSTICS_UPLOADER;
const auto constants = this->scripts_path / CONSTANTS;
std::vector<std::string> args = {constants.string(), upload_logs_request.location, diagnostics_file_name,
diagnostics_file_path.string()};
bool uploaded = false;
int32_t retries = 0;
const auto total_retries = upload_logs_request.retries.value_or(this->mod->config.DefaultRetries);
const auto retry_interval =
upload_logs_request.retry_interval_s.value_or(this->mod->config.DefaultRetryInterval);
types::system::LogStatus log_status;
if (create_result == DiagnosticsHandler::log_result_t::error_file) {
// problem creating the file - nothing to upload
log_status.log_status = types::system::LogStatusEnum::UploadFailure;
this->publish_log_status(log_status);
} else {
while (!uploaded && retries <= total_retries && !this->interrupt_log_upload) {
retries += 1;
log_status.request_id = upload_logs_request.request_id.value_or(-1);
run_application(
diagnostics_uploader.string(), args, [this, &log_status](const std::string& output_line) {
if (output_line == "Uploaded") {
log_status.log_status = types::system::string_to_log_status_enum(output_line);
} else if (output_line == "UploadFailure" || output_line == "PermissionDenied" ||
output_line == "BadMessage" || output_line == "NotSupportedOperation") {
log_status.log_status = types::system::LogStatusEnum::UploadFailure;
} else {
log_status.log_status = types::system::LogStatusEnum::Uploading;
}
this->publish_log_status(log_status);
if (this->interrupt_log_upload) {
return CmdControl::Terminate;
}
return CmdControl::Continue;
});
if (this->interrupt_log_upload) {
EVLOG_info << "Uploading Logs was interrupted, terminating upload script, requestId: "
<< log_status.request_id;
// N01.FR.20
log_status.log_status = types::system::LogStatusEnum::AcceptedCanceled;
this->publish_log_status(log_status);
} else if (log_status.log_status != types::system::LogStatusEnum::Uploaded &&
retries <= total_retries) {
std::this_thread::sleep_for(std::chrono::seconds(retry_interval));
} else {
uploaded = true;
}
}
}
this->log_upload_running = false;
this->log_upload_cv.notify_one();
EVLOG_info << "Log upload thread finished";
});
this->upload_logs_thread.detach();
return response;
}
bool systemImpl::handle_is_reset_allowed(types::system::ResetType& type) {
// Allow resets at any time for now
return true;
}
void systemImpl::handle_reset(types::system::ResetType& type, bool& scheduled) {
if (type == types::system::ResetType::Soft) {
EVLOG_info << "Performing soft reset";
// This will effectivly stop everest and make it restart via systemd
exit(255);
} else {
EVLOG_info << "Performing hard reset";
// this reboots the whole linux system
system("/sbin/reboot");
}
}
bool systemImpl::handle_set_system_time(std::string& timestamp) {
// currently not supported, system runs on network time
return true;
}
types::system::BootReason systemImpl::handle_get_boot_reason() {
return types::system::BootReason::Unknown;
}
void systemImpl::handle_allow_firmware_installation() {
EVLOG_info << "Received allow_firmware_installation command - allow firmware update to proceed with reboot.";
this->mod->firmware_update_may_proceed_with_reboot_callback();
}
} // namespace main
} // namespace module

View File

@@ -0,0 +1,76 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
#ifndef MAIN_SYSTEM_IMPL_HPP
#define MAIN_SYSTEM_IMPL_HPP
//
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
// template version 3
//
#include <generated/interfaces/system/Implementation.hpp>
#include "../Linux_Systemd_Rauc.hpp"
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
// insert your custom include headers here
#include <filesystem>
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
namespace module {
namespace main {
struct Conf {};
class systemImpl : public systemImplBase {
public:
systemImpl() = delete;
systemImpl(Everest::ModuleAdapter* ev, const Everest::PtrContainer<Linux_Systemd_Rauc>& mod, Conf& config) :
systemImplBase(ev, "main"), mod(mod), config(config){};
// ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1
// insert your public definitions here
// ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1
protected:
// command handler functions (virtual)
virtual types::system::UpdateFirmwareResponse
handle_update_firmware(types::system::FirmwareUpdateRequest& firmware_update_request) override;
virtual void handle_allow_firmware_installation() override;
virtual types::system::UploadLogsResponse
handle_upload_logs(types::system::UploadLogsRequest& upload_logs_request) override;
virtual bool handle_is_reset_allowed(types::system::ResetType& type) override;
virtual void handle_reset(types::system::ResetType& type, bool& scheduled) override;
virtual bool handle_set_system_time(std::string& timestamp) override;
virtual types::system::BootReason handle_get_boot_reason() override;
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
// insert your protected definitions here
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
private:
const Everest::PtrContainer<Linux_Systemd_Rauc>& mod;
const Conf& config;
virtual void init() override;
virtual void ready() override;
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
// insert your private definitions here
std::filesystem::path scripts_path;
bool log_upload_running{false};
std::atomic_bool interrupt_log_upload;
std::thread upload_logs_thread;
std::mutex log_upload_mutex;
std::condition_variable log_upload_cv;
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
};
// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1
// insert other definitions here
// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1
} // namespace main
} // namespace module
#endif // MAIN_SYSTEM_IMPL_HPP

View File

@@ -0,0 +1,45 @@
description: This module implements system wide operations for the base linux system
config:
DefaultRetries:
description: Specifies how many times Charge Point tries to upload or download files on previous failure.
type: number
default: 1
DefaultRetryInterval:
description: >-
Specifies in seconds after which time a retry of an upload or download on previous failure may be attempted.
type: number
default: 1
OCPPLogPath:
description: Path to folder where logs of all OCPP messages get written to (see ocpp MessageLogPath)
type: string
default: /var/log/everest/ocpp
SessionLogPath:
description: Output directory for session log files (see evse_manager session_logging_path)
type: string
default: /var/log/everest/session
RebootCommand:
description: Command to execute for rebooting the system
type: string
default: /sbin/reboot
VerifyUpdateScriptPath:
description: >-
Full path to shell script that checks if an OTA update has been successful. The script is executed after
an update and shall verify the success of the update. It shall return 0 in case of success and
non-zero in case of failure.
If empty no check is performed and the update is considered successful and it is marked as good.
type: string
default: ""
provides:
main:
description: Implements the system interface
interface: system
requires:
store:
interface: kvs
enable_external_mqtt: false
metadata:
license: https://opensource.org/licenses/Apache-2.0
authors:
- Piet Gömpel
- Cornelius Claussen
- James Chapman

View File

@@ -0,0 +1,165 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
#include "rauc_dbus.hpp"
#include <everest/logging.hpp>
namespace module {
using namespace everest::lib::system;
void Rauc::configure_handlers() {
namespace interface = rauc_dbus::interface;
namespace property = rauc_dbus::property;
namespace signal = rauc_dbus::signal;
// Subscribe to Complete signal (when install_bundle command finishes)
dbus::registerSignalHandler(proxy, interface::Installer, signal::Completed, [this](sdbus::Signal signal) {
// Complete signal has one int argument
int i;
signal >> i;
if (update_request_id != request_id_default) {
if (i == 0) {
EVLOG_info << "RAUC: Installation successful, needs reboot to activate";
// Signal to the module code to store the transaction in the database.
// We will use this on next boot to signal a Success/Failed Installation
signal_store_update_transaction(create_transaction(update_request_id, timeout_us));
// The module code should reboot now since we signal InstallRebooting.
signal_firmware_update_status(types::system::FirmwareUpdateStatusEnum::InstallRebooting,
update_request_id);
} else {
EVLOG_error << "RAUC: Installation failed with error code: " << i;
if (is_installing) {
signal_firmware_update_status(types::system::FirmwareUpdateStatusEnum::InstallVerificationFailed,
update_request_id);
is_installing = false;
}
}
} else {
EVLOG_debug << "RAUC: status from another source";
}
// this was the last message, so reset request_id
update_request_id = request_id_default;
});
// Subscribe to property changes std::function<void(Signal signal)>;
dbus::registerSignalHandler(
proxy, interface::DBus_Properties, signal::PropertiesChanged, [this](sdbus::Signal signal) {
// org.freedesktop.DBus.Properties.PropertiesChanged (STRING interface_name,
// ARRAY of DICT_ENTRY<STRING,VARIANT>
// changed_properties, ARRAY<STRING>
// invalidated_properties);
std::string interface;
signal >> interface;
// ignore updates when initiated by someone else
if (update_request_id != request_id_default) {
if (interface == interface::Installer) {
std::map<std::string, sdbus::Variant> changed_properties;
signal >> changed_properties;
for (const auto& [key, value] : changed_properties) {
if (key == property::Progress) {
Progress r = value.get<Progress>();
EVLOG_info << "Progress " << r.percent << "% " << r.description;
// Map progress to OCPP structs
if (r.description.find("Verifying signature done") != std::string::npos) {
signal_firmware_update_status(types::system::FirmwareUpdateStatusEnum::Downloaded,
update_request_id);
signal_firmware_update_status(
types::system::FirmwareUpdateStatusEnum::SignatureVerified, update_request_id);
signature_verified = true;
} else if (r.description.find("Verifying signature failed") != std::string::npos) {
signal_firmware_update_status(types::system::FirmwareUpdateStatusEnum::Downloaded,
update_request_id);
signal_firmware_update_status(types::system::FirmwareUpdateStatusEnum::InvalidSignature,
update_request_id);
signature_verified = true;
// If bundle checking failed but we never got to signature verification download must
// have failed
} else if (!signature_verified &&
r.description.find("Checking bundle failed") != std::string::npos) {
signal_firmware_update_status(types::system::FirmwareUpdateStatusEnum::DownloadFailed,
update_request_id);
} else if (r.description.find("Copying") != std::string::npos &&
r.description.find("done") == std::string::npos) {
is_installing = true;
signal_firmware_update_status(types::system::FirmwareUpdateStatusEnum::Installing,
update_request_id);
}
} else if (key == property::Operation) {
auto r = rauc_dbus::rauc_messages::string_to_operation(value.get<std::string>());
if (r == Operation::Idle) {
EVLOG_info << "RAUC operation: Idle";
} else if (r == Operation::Installing) {
EVLOG_info << "RAUC operation: Installing";
}
}
}
std::vector<std::string> invalidated_properties;
signal >> invalidated_properties;
}
}
});
}
bool Rauc::decide_if_good(const rauc_dbus::rauc_messages::UpdateTransaction& saved, const CurrentState& current) {
// The original approach uses the primary slot, however this might not be
// as reliable as hoped. A change in boot slot should be more reliable
// however prior to this change the boot slot wasn't saved
bool result{false};
if (RaucBase::decide_if_good(saved, current)) {
if (saved.boot_slot.empty()) {
// use the previous approach
EVLOG_warning << "OTA: fallback to using primary slot";
result = saved.primary_slot == current.primary_slot;
} else {
// use the new approach
result = saved.boot_slot != current.boot_slot;
}
}
return result;
}
// Call on boot and pass a previous transaction that was not closed yet
void Rauc::check_previous_transaction(UpdateTransaction t) {
signal_remove_update_transaction();
if (rauc_dbus::RaucBaseSync::check_previous_transaction(t, timeout_us)) {
signal_firmware_update_status(types::system::FirmwareUpdateStatusEnum::Installed, t.request_id);
} else {
signal_firmware_update_status(types::system::FirmwareUpdateStatusEnum::InstallationFailed, t.request_id);
}
}
// Returns immediately. Progress is signalled with signal_firmware_update_status
rauc_dbus::rauc_messages::CmdResult Rauc::install_bundle(const std::string& filename, int32_t request_id) {
signature_verified = false;
is_installing = false;
update_request_id = request_id;
const auto ret = rauc_dbus::RaucBaseSync::install_bundle(filename, timeout_us);
if (ret.success) {
signal_firmware_update_status(types::system::FirmwareUpdateStatusEnum::Downloading, update_request_id);
} else {
update_request_id = request_id_default;
}
return ret;
}
bool Rauc::is_idle() {
// Note it is important to query rauc here as well as it may be busy with a local install
return (get_operation() == rauc_dbus::rauc_messages::Operation::Idle) && !is_installing;
}
} // namespace module

View File

@@ -0,0 +1,61 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
#ifndef RAUC_DBUS_HPP
#define RAUC_DBUS_HPP
#include <everest/system/rauc_dbus_base.hpp>
#include <sigslot/signal.hpp>
#include <generated/types/system.hpp>
#include <atomic>
#include <cstdint>
#include <unistd.h>
namespace module {
namespace rauc_dbus = everest::lib::system::rauc_dbus;
class Rauc : public rauc_dbus::RaucBaseSync {
public:
// Note OCPP defaults to -1 when not provided
static constexpr std::int32_t request_id_default = 0;
private:
constexpr static std::uint64_t timeout_us = 10 * 1000 * 1000; // 10 seconds
std::atomic<int32_t> update_request_id{request_id_default};
std::atomic_bool is_installing{false};
std::atomic_bool signature_verified{false};
void configure_handlers() override;
bool decide_if_good(const rauc_dbus::rauc_messages::UpdateTransaction& saved, const CurrentState& current) override;
public:
using CmdResult = rauc_dbus::rauc_messages::CmdResult;
using UpdateTransaction = rauc_dbus::rauc_messages::UpdateTransaction;
using Operation = rauc_dbus::rauc_messages::Operation;
using Progress = rauc_dbus::rauc_messages::Progress;
using rauc_dbus::RaucBaseSync::RaucBaseSync;
Rauc(sdbus::dont_run_event_loop_thread_t) = delete;
void check_previous_transaction(UpdateTransaction t);
bool is_idle();
CmdResult install_bundle(const std::string& filename, int32_t request_id);
void mark(const std::string& mark_s, const std::string& slot) {
rauc_dbus::RaucBaseSync::mark(mark_s, slot, timeout_us);
}
sigslot::signal<types::system::FirmwareUpdateStatusEnum, int32_t> signal_firmware_update_status;
// Emitted when installed update is ready for reboot, the transaction needs to be stored persistently. On next boot,
// call check_previous_transaction() with this as an argument
sigslot::signal<UpdateTransaction> signal_store_update_transaction;
sigslot::signal<> signal_remove_update_transaction;
};
} // namespace module
#endif