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,147 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
#include <charge_bridge/utilities/filesystem.hpp>
#include <cstring>
#include <fstream>
#include <iostream>
#include <limits>
#include <random>
namespace charge_bridge::filesystem_utils {
bool read_from_file_partial(const fs::path& file_path, const std::size_t byte_count, std::string& out_data) {
try {
if (fs::is_regular_file(file_path)) {
std::ifstream file(file_path, std::ios::binary);
if (file.is_open()) {
std::vector<char> buffer(byte_count);
file.read(buffer.data(), byte_count);
std::size_t read_bytes = file.gcount();
if (read_bytes == byte_count) {
out_data.assign(buffer.data(), read_bytes);
return true;
}
}
}
} catch (const std::exception& e) {
return false;
}
return false;
}
bool read_from_file(const fs::path& file_path, std::string& out_data) {
try {
if (fs::is_regular_file(file_path)) {
std::ifstream file(file_path, std::ios::binary);
if (file.is_open()) {
out_data = std::string((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
return true;
}
}
} catch (const std::exception& e) {
return false;
}
return false;
}
bool process_file(const fs::path& file_path, std::size_t buffer_size,
std::function<bool(const std::vector<std::uint8_t>&, bool last_chunk)>&& func) {
std::ifstream file(file_path, std::ios::binary);
return process_file(file, buffer_size, std::move(func));
}
bool process_file(std::ifstream& file, std::size_t buffer_size,
std::function<bool(const std::vector<std::uint8_t>&, bool last_chunk)>&& func) {
if (!file) {
return false;
}
std::vector<std::uint8_t> buffer(buffer_size);
bool interupted = false;
while (file.read(reinterpret_cast<char*>(buffer.data()), buffer_size)) {
interupted = func(buffer, false);
if (interupted) {
break;
}
}
// Process the remaining bytes
if (interupted == false) {
std::size_t remaining = file.gcount();
// Keep only remaining elements
buffer.resize(remaining);
func(buffer, true);
}
return true;
}
// Returns true on success, fills `hdr`, and sets `image_offset` to the byte
// position (from start of file) where the firmware image begins.
bool read_crypt_signed_header(const fs::path& path, CryptSignedHeader& hdr, std::uint32_t& image_offset) {
std::ifstream f(path, std::ios::binary);
if (!f) {
return false;
}
auto read_exact = [&](void* dst, std::size_t n) -> bool {
f.read(reinterpret_cast<char*>(dst), static_cast<std::streamsize>(n));
return f.good() || (f.eof() && static_cast<std::size_t>(f.gcount()) == n);
};
char firmware_version_str[32];
std::memset(firmware_version_str, 0, sizeof(firmware_version_str)); // all zeros
// 32-byte reserved header
if (!read_exact(firmware_version_str, sizeof(firmware_version_str))) {
return false;
}
hdr.firmware_version = std::string(firmware_version_str);
// 1-byte signature length
if (!read_exact(&hdr.sig_len, 1)) {
return false;
}
// L-byte signature
hdr.signature.resize(hdr.sig_len);
if (hdr.sig_len > 0) {
if (!read_exact(hdr.signature.data(), hdr.signature.size())) {
return false;
}
}
// 1-byte NUM_SECTORS
if (!read_exact(&hdr.num_sectors, 1)) {
return false;
}
// 16-byte IV
if (!read_exact(hdr.iv.data(), hdr.iv.size())) {
return false;
}
// Where the firmware image starts:
// offset = 32 + 1 + L + 1 + 16
image_offset = static_cast<std::uint32_t>(f.tellg());
// As a sanity fallback, compute if tellg() failed:
// Disabled, since it is always false
// if (static_cast<std::streamoff>(image_offset)< 0) {
// image_offset = 32u + 1u + static_cast<std::uint64_t>(hdr.sig_len) + 1u + 16u;
// }
return true;
}
} // namespace charge_bridge::filesystem_utils

View File

@@ -0,0 +1,64 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
#include <charge_bridge/utilities/logging.hpp>
#include <iomanip>
namespace charge_bridge::utilities {
enum class color {
error,
success,
warning,
message,
unit,
standard,
terminal,
};
std::ostream& operator<<(std::ostream& s, color c) {
switch (c) {
case color::error:
s << "\033[31m";
break;
case color::success:
s << "\033[32m";
break;
case color::warning:
s << "\033[33m";
break;
case color::message:
s << "\033[37m";
break;
case color::unit:
s << "\033[1;37m";
break;
case color::terminal:
s << "\033[m";
break;
case color::standard:
default:
s << "\033[39;49m";
}
return s;
}
std::ostream& print_error(std::string const& device, std::string const& unit, int status) {
// clang-format off
auto ctrl =
status == 0 ? color::success :
status == -1 ? color::warning:
color::error;
std::cout << "[ " << ctrl << std::setw(13) << std::left << unit << color::terminal << " ] "
<< color::unit << std::setw(20) << device << color::terminal << " ";
if(status not_eq 0){
if(status == -1){
std::cout << color::standard << "WARNING ";
}
else{
std::cout << color::standard << "ERROR ( " << status << " ) ";
}
}
return std::cout << color::standard;
// clang-format on
}
} // namespace charge_bridge::utilities

View File

@@ -0,0 +1,415 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
#include "c4/yml/node.hpp"
#include <charge_bridge/utilities/parse_config.hpp>
#include <charge_bridge/utilities/string.hpp>
#include <charge_bridge/utilities/type_converters.hpp>
#include <everest_api_types/evse_board_support/API.hpp>
#include <everest_api_types/evse_board_support/codec.hpp>
#include <iostream>
#include <filesystem>
// clang-format off
#include <ryml_std.hpp>
#include <ryml.hpp>
// clang-format on
using namespace everest::lib::API::V1_0::types;
namespace {
static const int g_cb_port_management = 6000;
static const int g_cb_port_evse_bsp = 6001;
static const int g_cb_port_plc = 6002;
static const int g_cb_port_can0 = 6003;
static const int g_cb_port_serial_1 = 6004;
static const int g_cb_port_serial_2 = 6005;
static const std::uint16_t default_mqtt_ping_interval_ms = 1000;
std::string print_yaml_location(ryml::Location const& loc) {
std::stringstream error_msg;
if (loc) {
if (not loc.name.empty()) {
auto tmp = std::string(loc.name.str, loc.name.len);
if (charge_bridge::utilities::string_ends_with(tmp, ".hpp")) {
return "";
}
error_msg << "\n file ";
error_msg << tmp;
}
error_msg << "\n line " << loc.line;
if (loc.col) {
error_msg << " column " << loc.col;
}
if (loc.offset) {
error_msg << " offset " << loc.offset << "B";
}
error_msg << "\n";
}
return error_msg.str();
}
void yaml_error_handler(const char* msg, std::size_t len, ryml::Location loc, void*) {
std::stringstream error_msg;
error_msg << "YAML parsing error: ";
error_msg << print_yaml_location(loc);
error_msg.write(msg, len);
std::cerr << error_msg.str() << std::endl;
throw std::runtime_error(error_msg.str());
}
void print_location(ryml::ConstNodeRef node, ryml::Parser& parser) {
std::cerr << print_yaml_location(node.location(parser)) << std::endl;
}
void load_yaml_file(const std::string& filename, ryml::Parser* parser, ryml::Tree* t) {
std::ifstream file(filename);
if (!file.is_open()) {
throw std::runtime_error("Could not open file: " + filename);
}
std::stringstream buffer;
buffer << file.rdbuf();
std::string file_content = buffer.str();
parse_in_arena(parser, ryml::to_csubstr(filename), ryml::to_csubstr(file_content), t);
}
template <class T> c4::yml::ConstNodeRef decode(c4::yml::ConstNodeRef const& node, T& rhs) {
using namespace charge_bridge::utilities;
node >> rhs;
return node;
}
std::pair<std::string, c4::yml::ConstNodeRef> find_node(c4::yml::NodeRef& config, std::string const& main,
std::string const& sub) {
auto main_str = ryml::to_csubstr(main);
auto node_str = main;
c4::yml::ConstNodeRef node;
if (not sub.empty()) {
node_str = node_str + "::" + sub;
auto sub_str = ryml::to_csubstr(sub);
node = config.find_child(main_str);
if (not node.invalid()) {
node = config.find_child(main_str).find_child(sub_str);
}
} else {
node = config[main_str];
}
return {node_str, node};
}
template <class DataT>
bool get_node_impl(c4::yml::ConstNodeRef node, ryml::Parser& parser, std::string const& node_str, DataT& data) {
if (node.invalid()) {
std::cerr << "Node not found: " << node_str << std::endl;
throw std::runtime_error("");
}
try {
decode(node, data);
return true;
} catch (std::exception const& e) {
std::cerr << "Cannot parse config: " << node_str << std::endl;
std::cerr << e.what() << std::endl;
} catch (charge_bridge::utilities::yml_node_error const& e) {
std::cerr << "Error source: \n"
<< " parent " << node_str << "\n"
<< " data " << e.m_msg << std::flush;
print_location(e.m_node, parser);
}
throw std::runtime_error("");
}
struct RymlCallbackInitializer {
RymlCallbackInitializer() {
ryml::set_callbacks({nullptr, nullptr, nullptr, yaml_error_handler});
}
};
} // namespace
namespace charge_bridge::utilities {
void parse_config_impl(c4::yml::NodeRef& config, charge_bridge_config& c, std::filesystem::path const& config_path,
ryml::Parser& parser) {
auto get_node = [&config, &parser](auto& data, std::string const& main, std::string const& sub = "") {
auto [node_str, node] = find_node(config, main, sub);
get_node_impl(node, parser, node_str, data);
};
auto get_node_or_default = [&get_node, &config](auto& data, std::string const& main, std::string const& sub,
auto fallback) {
auto [node_str, node] = find_node(config, main, sub);
if (node.invalid()) {
data = fallback;
return;
}
try {
get_node(data, main, sub);
} catch (...) {
data = fallback;
}
};
auto get_block = [&config, &c](std::string const& block, auto& block_cfg, auto const& ftor) {
bool enable = false;
auto block_str = ryml::to_csubstr(block);
if (not config.find_child(block_str).invalid()) {
if (config[block_str].find_child("enable").invalid()) {
enable = true;
} else {
decode(config[block_str]["enable"], enable);
}
}
if (enable) {
block_cfg.emplace();
ftor(*block_cfg, block);
block_cfg->cb = c.cb_name;
block_cfg->item = block;
}
};
get_node(c.cb_name, "charge_bridge", "name");
get_node(c.cb_remote, "charge_bridge", "ip");
c.cb_port = g_cb_port_management;
get_block("can_0", c.can0, [&](auto& cfg, auto const& main) {
get_node(cfg.can_device, main, "local");
cfg.cb_port = g_cb_port_can0;
cfg.cb_remote = c.cb_remote;
});
get_block("serial_1", c.serial1, [&](auto& cfg, auto const& main) {
get_node(cfg.serial_device, main, "local");
cfg.cb_port = g_cb_port_serial_1;
cfg.cb_remote = c.cb_remote;
});
get_block("serial_2", c.serial2, [&](auto& cfg, auto const& main) {
get_node(cfg.serial_device, main, "local");
cfg.cb_port = g_cb_port_serial_2;
cfg.cb_remote = c.cb_remote;
});
// FIXME (JH) serial3 not availabe in first release
// get_block("serial_3", c.serial3, [&](auto& cfg, auto const& main) {
// get_node(main, "local", cfg.serial_device);
// get_node(main, "port", cfg.cb_port);
// cfg.cb_remote = c.cb_remote;
// });
get_block("plc", c.plc, [&](auto& cfg, auto const& main) {
get_node(cfg.plc_tap, main, "tap");
get_node(cfg.plc_ip, main, "ip");
get_node(cfg.plc_netmaks, main, "netmask");
get_node(cfg.plc_mtu, main, "mtu");
cfg.cb_port = g_cb_port_plc;
cfg.cb_remote = c.cb_remote;
});
{
bool wants_ev = false;
bool wants_evse = false;
get_node_or_default(wants_ev, "ev_bsp", "enable", false);
get_node_or_default(wants_evse, "evse_bsp", "enable", false);
if (wants_ev && wants_evse) {
std::cerr << "Configuration error: Cannot enable EVSE and EV BSP at the same time" << std::endl;
throw std::exception();
}
}
get_block("evse_bsp", c.bsp, [&](auto& cfg, auto const& main) {
cfg.cb_port = g_cb_port_evse_bsp;
cfg.api.evse.enabled = true;
get_node(cfg.api.evse.module_id, main, "module_id");
get_node(cfg.api.mqtt_remote, main, "mqtt_remote");
get_node_or_default(cfg.api.mqtt_bind, main, "mqtt_bind", "");
get_node(cfg.api.mqtt_port, main, "mqtt_port");
get_node_or_default(cfg.api.mqtt_ping_interval_ms, main, "mqtt_ping_interval_ms",
default_mqtt_ping_interval_ms);
cfg.cb_remote = c.cb_remote;
get_node(cfg.api.evse.capabilities, main, "capabilities");
get_node(cfg.api.ovm.enabled, main, "ovm_enabled");
get_node(cfg.api.ovm.module_id, main, "ovm_module_id");
});
if (not c.bsp.has_value()) {
get_block("ev_bsp", c.bsp, [&](auto& cfg, auto const& main) {
cfg.cb_port = g_cb_port_evse_bsp;
cfg.api.ev.enabled = true;
get_node(cfg.api.ev.module_id, main, "module_id");
get_node(cfg.api.mqtt_remote, main, "mqtt_remote");
get_node_or_default(cfg.api.mqtt_bind, main, "mqtt_bind", "");
get_node(cfg.api.mqtt_port, main, "mqtt_port");
get_node_or_default(cfg.api.mqtt_ping_interval_ms, main, "mqtt_ping_interval_ms",
default_mqtt_ping_interval_ms);
cfg.cb_remote = c.cb_remote;
get_node(cfg.api.ovm.enabled, main, "ovm_enabled");
get_node(cfg.api.ovm.module_id, main, "ovm_module_id");
});
}
get_block("gpio", c.gpio, [&](auto& cfg, auto const& main) {
get_node(cfg.interval_s, main, "interval_s");
get_node(cfg.mqtt_remote, main, "mqtt_remote");
get_node_or_default(cfg.mqtt_bind, main, "mqtt_bind", "");
get_node(cfg.mqtt_port, main, "mqtt_port");
get_node_or_default(cfg.mqtt_ping_interval_ms, main, "mqtt_ping_interval_ms", default_mqtt_ping_interval_ms);
cfg.cb_remote = c.cb_remote;
cfg.cb_port = c.cb_port;
});
get_block("heartbeat", c.heartbeat, [&](auto& cfg, auto const& main) {
get_node_or_default(cfg.interval_s, main, "interval_s", 1);
get_node_or_default(cfg.connection_to_s, main, "connection_to_s", 3 * cfg.interval_s);
cfg.cb_remote = c.cb_remote;
cfg.cb_port = c.cb_port;
get_node(cfg.cb_config.network, "charge_bridge");
get_node(cfg.cb_config.safety, "safety");
std::memset(cfg.cb_config.gpios, 0, CB_NUMBER_OF_GPIOS * sizeof(CbGpioConfig));
std::memset(cfg.cb_config.uarts, 0, CB_NUMBER_OF_UARTS * sizeof(CbUartConfig));
if (c.serial1) {
get_node(cfg.cb_config.uarts[0], "serial_1");
}
if (c.serial2) {
get_node(cfg.cb_config.uarts[1], "serial_2");
}
// FIXME (JH) serial 3 not available in first release
// if (c.serial3) {
// get_main_node("serial_3", cfg.cb_config.uarts[2]);
// }
if (c.gpio) {
for (auto i = 0; i < CB_NUMBER_OF_GPIOS; ++i) {
get_node(cfg.cb_config.gpios[i], "gpio", "gpio_" + std::to_string(i));
}
}
if (c.can0) {
get_node(cfg.cb_config.can, "can_0");
}
get_node(cfg.cb_config.plc_powersaving_mode, "plc", "powersaving_mode");
cfg.cb_config.config_version = CB_CONFIG_VERSION;
});
get_node(c.firmware.fw_path, "charge_bridge", "fw_file");
get_node(c.firmware.fw_update_on_start, "charge_bridge", "fw_update_on_start");
// If the path to the firmware file is relative, make it relative to the config file
std::filesystem::path fw_path = c.firmware.fw_path;
if (fw_path.is_relative()) {
c.firmware.fw_path = config_path.parent_path().append(c.firmware.fw_path);
}
c.firmware.cb_remote = c.cb_remote;
c.firmware.cb_port = c.cb_port;
c.firmware.cb = c.cb_name;
}
charge_bridge_config set_config_placeholders(charge_bridge_config const& src, charge_bridge_config& result,
std::string const& ip, std::size_t index) {
auto index_str = std::to_string(index);
result = src;
auto replace = [index_str](std::string& src) { replace_all_in_place(src, "##", index_str); };
result.cb_remote = ip;
result.firmware.cb_remote = ip;
replace(result.cb_name);
result.firmware.cb = result.cb_name;
if (result.can0.has_value()) {
result.can0->cb_remote = ip;
result.can0->cb = result.cb_name;
replace(result.can0->can_device);
}
if (result.serial1.has_value()) {
result.serial1->cb_remote = ip;
result.serial1->cb = result.cb_name;
replace(result.serial1->serial_device);
}
if (result.serial2.has_value()) {
result.serial2->cb_remote = ip;
result.serial2->cb = result.cb_name;
replace(result.serial2->serial_device);
}
if (result.serial3.has_value()) {
result.serial3->cb_remote = ip;
result.serial3->cb = result.cb_name;
replace(result.serial3->serial_device);
}
if (result.plc.has_value()) {
result.plc->cb_remote = ip;
result.plc->cb = result.cb_name;
replace(result.plc->plc_tap);
}
if (result.bsp.has_value()) {
result.bsp->cb_remote = ip;
result.bsp->cb = result.cb_name;
replace(result.bsp->api.evse.module_id);
replace(result.bsp->api.ev.module_id);
replace(result.bsp->api.ovm.module_id);
}
if (result.heartbeat.has_value()) {
result.heartbeat->cb = result.cb_name;
result.heartbeat->cb_remote = ip;
}
if (result.gpio.has_value()) {
result.gpio->cb = result.cb_name;
result.gpio->cb_remote = ip;
}
if (result.heartbeat.has_value()) {
auto& raw = result.heartbeat->cb_config.network.mdns_name;
std::string item = raw;
replace(item);
auto limit = sizeof(raw);
if (item.size() > limit) {
item = "cb_" + index_str;
std::cout << "WARNING: Replacement for mdns_name is too long. Fallback to '" + item + "'" << std::endl;
}
std::memset(raw, 0, limit);
std::memcpy(raw, item.c_str(), std::min(item.size(), limit));
result.heartbeat->cb_remote = ip;
result.heartbeat->cb = result.cb_name;
}
return result;
}
std::vector<charge_bridge_config> parse_config_multi(std::string const& config_file) {
const static RymlCallbackInitializer ryml_callback_initializer;
try {
ryml::EventHandlerTree evt_handler = {};
ryml::Parser parser(&evt_handler, ryml::ParserOptions().locations(true));
ryml::Tree config_tree;
load_yaml_file(config_file, &parser, &config_tree);
c4::yml::NodeRef config = config_tree.rootref();
if (config.invalid()) {
std::cerr << "Config file not found: " << config_file << std::endl;
return {};
}
charge_bridge_config base_config;
parse_config_impl(config, base_config, config_file, parser);
auto ip_list_node = config.find_child("charge_bridge_ip_list");
if (ip_list_node.invalid()) {
return {base_config};
}
std::vector<std::string> ip_list;
ip_list_node >> ip_list;
std::vector<charge_bridge_config> cb_config_list(ip_list.size());
for (std::size_t i = 0; i < ip_list.size(); ++i) {
set_config_placeholders(base_config, cb_config_list[i], ip_list[i], i);
}
return cb_config_list;
} catch (...) {
std::cerr << "FAILED to parse configuration!" << std::endl;
}
return {};
}
} // namespace charge_bridge::utilities

View File

@@ -0,0 +1,97 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
#include "protocol/cb_config.h"
#include <charge_bridge/charge_bridge.hpp>
#include <sstream>
namespace charge_bridge::utilities {
std::string to_string(CbCanBaudrate value) {
switch (value) {
case CBCBR_125000:
return "125000";
case CBCBR_250000:
return "250000";
case CBCBR_500000:
return "500000";
case CBCBR_1000000:
return "1000000";
default:
break;
}
return "Invalid bitrate";
}
std::string to_string(CbUartBaudrate value) {
switch (value) {
case CBUBR_9600:
return "9600";
case CBUBR_19200:
return "19200";
case CBUBR_38400:
return "38400";
case CBUBR_57600:
return "57600";
case CBUBR_115200:
return "115200";
case CBUBR_230400:
return "230400";
case CBUBR_250000:
return "250000";
case CBUBR_460800:
return "460800";
case CBUBR_500000:
return "500000";
case CBUBR_1000000:
return "1000000";
case CBUBR_2000000:
return "2000000";
case CBUBR_3000000:
return "3000000";
case CBUBR_4000000:
return "4000000";
case CBUBR_6000000:
return "6000000";
case CBUBR_8000000:
return "8000000";
case CBUBR_10000000:
return "10000000";
default:
break;
}
return "Invalid baudrate";
}
std::string to_string(CbUartParity value) {
switch (value) {
case CBUP_None:
return "N";
case CBUP_Odd:
return "O";
case CBUP_Even:
return "E";
default:
break;
}
return "Invalid parity";
}
std::string to_string(CbUartStopbits value) {
switch (value) {
case CBUS_OneStopBit:
return "1";
case CBUS_TwoStopBits:
return "2";
default:
break;
}
return "Invalid parity";
}
std::string to_string(CbUartConfig const& value) {
std::stringstream data;
data << to_string(value.baudrate) << " 8" << to_string(value.parity) << to_string(value.stopbits);
return data.str();
}
} // namespace charge_bridge::utilities

View File

@@ -0,0 +1,59 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
#include <charge_bridge/utilities/string.hpp>
#include <sstream>
namespace charge_bridge::utilities {
bool string_starts_with(std::string_view const& str, std::string_view const& pattern) {
return str.rfind(pattern, 0) == 0;
}
bool string_ends_with(std::string const& str, std::string const& pattern) {
if (pattern.size() > str.size())
return false;
return std::equal(pattern.rbegin(), pattern.rend(), str.rbegin());
}
std::string string_after_pattern(std::string_view const& str, std::string_view const& pattern) {
if (charge_bridge::utilities::string_starts_with(str, pattern)) {
return static_cast<std::string>(str.substr(pattern.length()));
}
return "";
}
std::string& replace_all_in_place(std::string& source, std::string const& placeholder, std::string const& substitute) {
if (placeholder.empty()) {
return source;
}
std::size_t start_pos = 0;
while ((start_pos = source.find(placeholder, start_pos)) != std::string::npos) {
source.replace(start_pos, placeholder.length(), substitute);
start_pos += substitute.length();
}
return source;
}
std::string replace_all(std::string const& source, std::string const& placeholder, std::string const& substitute) {
std::string result = source;
return replace_all_in_place(result, placeholder, substitute);
}
std::set<std::string> csv_to_set(std::string const& str) {
std::set<std::string> result;
std::stringstream ss(str);
std::string item;
while (std::getline(ss, item, ',')) {
if (!item.empty()) {
result.insert(item);
}
}
return result;
}
} // namespace charge_bridge::utilities

View File

@@ -0,0 +1,51 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
#include <charge_bridge/utilities/symlink.hpp>
#include <fcntl.h>
#include <unistd.h>
namespace charge_bridge::utilities {
symlink::symlink() {
}
symlink::symlink(std::string const& src, std::string const& tar) {
auto result = set_link(src, tar);
if (not result) {
std::string msg = "Cannot create symbolic link from '" + src + "' to '" + tar + "'";
perror(msg.c_str());
}
}
bool symlink::set_link(std::string const& src, std::string const& tar) {
m_tar = tar;
del_link();
auto result = ::symlink(src.c_str(), tar.c_str()) == 0;
if (result) {
m_tar = tar;
}
return result;
}
bool symlink::del_link() {
auto result = true;
if (not m_tar.empty()) {
auto code = ::unlink(m_tar.c_str());
result = code == 0 or code == ENOENT;
m_tar = "";
}
return result;
}
symlink::~symlink() {
if (not m_tar.empty()) {
auto result = del_link();
if (not result) {
std::string msg = "Cannot delete symbolic link '" + m_tar + "'";
perror(msg.c_str());
}
}
}
} // namespace charge_bridge::utilities

View File

@@ -0,0 +1,83 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
#include "everest/io/event/fd_event_handler.hpp"
#include <charge_bridge/utilities/sync_udp_client.hpp>
#include <chrono>
#include <optional>
namespace charge_bridge::utilities {
using reply = sync_udp_client::reply;
using namespace std::chrono_literals;
sync_udp_client::sync_udp_client(std::string const& remote, std::uint16_t port) : m_retries(0), m_timeout_ms(1000) {
init(remote, port);
}
sync_udp_client::sync_udp_client(std::string const& remote, std::uint16_t port, std::uint16_t retries,
std::uint16_t timeout_ms) :
m_retries(retries), m_timeout_ms(timeout_ms) {
init(remote, port);
}
void sync_udp_client::init(std::string const& remote, std::uint16_t port) {
m_udp.open_as_client(remote, port);
m_handler.register_event_handler(
m_udp.get_fd(), [this](auto) {}, everest::lib::io::event::poll_events::read);
}
reply sync_udp_client::request_reply(udp_payload const& payload) {
return request_reply(payload, m_timeout_ms, m_retries);
}
reply sync_udp_client::request_reply(udp_payload const& payload, std::uint16_t timeout_ms, std::uint16_t retries) {
udp_payload result;
clear_socket();
if (not m_udp.tx(payload)) {
return std::nullopt;
}
for (std::uint16_t i = 0; i < retries; ++i) {
if (not m_handler.poll(std::chrono::milliseconds(timeout_ms))) {
if (not m_udp.tx(payload)) {
return std::nullopt;
}
continue;
}
if (not m_udp.rx(result)) {
return std::nullopt;
}
return result;
}
return std::nullopt;
}
bool sync_udp_client::tx(udp_payload const& payload) {
return m_udp.tx(payload);
}
reply sync_udp_client::rx() {
return rx(m_timeout_ms);
}
reply sync_udp_client::rx(std::uint16_t timeout_ms) {
udp_payload result;
if (not m_handler.poll(std::chrono::milliseconds(timeout_ms))) {
return std::nullopt;
}
if (not m_udp.rx(result)) {
return std::nullopt;
}
return result;
}
bool sync_udp_client::is_open() {
return m_udp.is_open();
}
void sync_udp_client::clear_socket() {
udp_payload tmp;
while (m_handler.poll(0ms)) {
m_udp.rx(tmp);
};
}
} // namespace charge_bridge::utilities

View File

@@ -0,0 +1,610 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
#include "protocol/cb_config.h"
#include <charge_bridge/utilities/type_converters.hpp>
#include <cmath>
#include <everest_api_types/evse_board_support/API.hpp>
#include <everest_api_types/evse_board_support/codec.hpp>
#include <everest_api_types/utilities/codec.hpp>
// clang-format off
#include <optional>
#include <ryml_std.hpp>
#include <ryml.hpp>
// clang-format on
#include <functional>
#include <iostream>
namespace {
template <class T> void decode(c4::yml::ConstNodeRef const& node, T& rhs) {
using namespace charge_bridge::utilities;
try {
node >> rhs;
} catch (...) {
std::string value;
std::string key;
if (node.has_key()) {
key = std::string(node.key().str, node.key().len);
}
if (node.has_val()) {
value = std::string(node.val().str, node.val().len);
}
throw charge_bridge::utilities::yml_node_error(node, key + "::" + value);
}
}
template <class T, class TmpT>
T decode_t(c4::yml::ConstNodeRef parent, std::string const& node_id, std::function<T(TmpT)> const& transform,
std::optional<T> const& def_value = std::nullopt) {
auto index = ryml::to_csubstr(node_id);
auto local_node = parent.find_child(index);
if (local_node.invalid()) {
if (def_value.has_value()) {
return def_value.value();
}
throw charge_bridge::utilities::yml_node_error(parent, "Cannot find nested node: " + node_id);
}
TmpT tmp;
decode(local_node, tmp);
return transform(tmp);
}
template <class Ftor, class T>
auto decode_t(c4::yml::ConstNodeRef parent, std::string const& node_id, Ftor transform, std::optional<T> const& t) {
std::function g = transform;
return decode_t(parent, node_id, g, t);
}
template <class Ftor> auto decode_t(c4::yml::ConstNodeRef parent, std::string const& node_id, Ftor transform) {
std::function g = transform;
return decode_t(parent, node_id, g);
}
template <class T>
T decode(c4::yml::ConstNodeRef parent, std::string const& node_id, std::optional<T> const& def_value = std::nullopt) {
auto identity = [](T v) { return v; };
return decode_t(parent, node_id, identity, def_value);
}
} // namespace
namespace charge_bridge::utilities {
yml_node_error::yml_node_error(c4::yml::ConstNodeRef node) : m_node(node) {
}
yml_node_error::yml_node_error(c4::yml::ConstNodeRef node, std::string const& msg) : m_node(node), m_msg(msg) {
}
bool decode_CbGpioMode(c4::yml::ConstNodeRef const& node, CbGpioMode& rhs) {
if (node.invalid()) {
return false;
}
std::string value;
decode(node, value);
if (value == "Input") {
rhs = CbGpioMode::CBG_Input;
return true;
} else if (value == "Output") {
rhs = CbGpioMode::CBG_Output;
return true;
} else if (value == "Pwm_Input") {
rhs = CbGpioMode::CBG_Pwm_Input;
return true;
} else if (value == "Pwm_Output") {
rhs = CbGpioMode::CBG_Pwm_Output;
return true;
} else if (value == "RS485_2_DE") {
rhs = CbGpioMode::CBG_RS485_2_DE;
return true;
} else if (value == "Rcd_Selftest_Output") {
rhs = CbGpioMode::CBG_Rcd_Selftest_Output;
return true;
} else if (value == "Rcd_Error_Input") {
rhs = CbGpioMode::CBG_Rcd_Error_Input;
return true;
} else if (value == "Rcd_PWM_Input") {
rhs = CbGpioMode::CBG_Rcd_PWM_Input;
return true;
} else if (value == "MotorLock_1") {
rhs = CbGpioMode::CBG_MotorLock_1;
return true;
} else if (value == "MotorLock_2") {
rhs = CbGpioMode::CBG_MotorLock_2;
return true;
}
throw yml_node_error(node);
return false;
}
bool decode_CbGpioPulls(c4::yml::ConstNodeRef const& node, CbGpioPulls& rhs) {
if (node.invalid()) {
return false;
}
std::string value;
decode(node, value);
if (value == "NoPull") {
rhs = CBGP_NoPull;
return true;
}
if (value == "PullUp") {
rhs = CBGP_PullUp;
return true;
}
if (value == "PullDown") {
rhs = CBGP_PullDown;
return true;
}
throw yml_node_error(node);
return false;
}
bool decode_CbUartBaudrate(c4::yml::ConstNodeRef const& node, CbUartBaudrate& rhs) {
if (node.invalid()) {
return false;
}
std::string value;
decode(node, value);
if (value == "9600") {
rhs = CBUBR_9600;
return true;
}
if (value == "19200") {
rhs = CBUBR_19200;
return true;
}
if (value == "38400") {
rhs = CBUBR_38400;
return true;
}
if (value == "57600") {
rhs = CBUBR_57600;
return true;
}
if (value == "115200") {
rhs = CBUBR_115200;
return true;
}
if (value == "230400") {
rhs = CBUBR_230400;
return true;
}
if (value == "250000") {
rhs = CBUBR_250000;
return true;
}
if (value == "460800") {
rhs = CBUBR_460800;
return true;
}
if (value == "500000") {
rhs = CBUBR_500000;
return true;
}
if (value == "1000000") {
rhs = CBUBR_1000000;
return true;
}
if (value == "2000000") {
rhs = CBUBR_2000000;
return true;
}
if (value == "3000000") {
rhs = CBUBR_3000000;
return true;
}
if (value == "4000000") {
rhs = CBUBR_4000000;
return true;
}
if (value == "6000000") {
rhs = CBUBR_6000000;
return true;
}
if (value == "8000000") {
rhs = CBUBR_8000000;
return true;
}
if (value == "10000000") {
rhs = CBUBR_10000000;
return true;
}
throw yml_node_error(node);
return false;
}
bool decode_CbUartStopbits(c4::yml::ConstNodeRef const& node, CbUartStopbits& rhs) {
if (node.invalid()) {
return false;
}
std::string value;
decode(node, value);
if (value == "OneStopBit") {
rhs = CBUS_OneStopBit;
return true;
}
if (value == "TwoStopBits") {
rhs = CBUS_TwoStopBits;
return true;
}
throw yml_node_error(node);
return false;
}
bool decode_CbUartParity(c4::yml::ConstNodeRef const& node, CbUartParity& rhs) {
if (node.invalid()) {
return false;
}
std::string value;
decode(node, value);
if (value == "None") {
rhs = CBUP_None;
return true;
}
if (value == "Odd") {
rhs = CBUP_Odd;
return true;
}
if (value == "Even") {
rhs = CBUP_Even;
return true;
}
throw yml_node_error(node);
return false;
}
bool decode_CbCanBaudrate(c4::yml::ConstNodeRef const& node, CbCanBaudrate& rhs) {
if (node.invalid()) {
return false;
}
std::string value;
decode(node, value);
if (value == "125000") {
rhs = CBCBR_125000;
return true;
}
if (value == "250000") {
rhs = CBCBR_250000;
return true;
}
if (value == "500000") {
rhs = CBCBR_500000;
return true;
}
if (value == "1000000") {
rhs = CBCBR_1000000;
return true;
}
throw yml_node_error(node);
return false;
}
bool decode_CbRelayMode(c4::yml::ConstNodeRef const& node, CbRelayMode& rhs) {
if (node.invalid()) {
return false;
}
std::string value;
decode(node, value);
if (value == "PowerRelay") {
rhs = CBR_PowerRelay;
return true;
}
if (value == "UserRelay") {
rhs = CBR_UserRelay;
return true;
}
throw yml_node_error(node);
return false;
}
bool decode_CbSafetyMode(c4::yml::ConstNodeRef const& node, CbSafetyMode& rhs) {
if (node.invalid()) {
return false;
}
std::string value;
decode(node, value);
if (value == "disabled") {
rhs = CBSM_disabled;
return true;
}
if (value == "US") {
rhs = CBSM_US;
return true;
}
if (value == "EU") {
rhs = CBSM_EU;
return true;
}
throw yml_node_error(node);
return false;
}
bool decode_RelayConfig(c4::yml::ConstNodeRef const& node, RelayConfig& rhs) {
using ryml::ConstNodeRef;
if (node.invalid()) {
return false;
}
rhs.relay_mode = decode<decltype(rhs.relay_mode)>(node, "relay_mode");
rhs.feedback_enabled = decode_t(node, "feedback_enabled", [](bool tmp) -> uint8_t { return tmp ? 1 : 0; });
rhs.feedback_delay_ms = decode<decltype(rhs.feedback_delay_ms)>(node, "feedback_delay_ms");
rhs.feedback_inverted = decode_t(node, "feedback_inverted", [](bool tmp) -> uint8_t { return tmp ? 1 : 0; });
rhs.pwm_dc = decode_t(node, "pwm_dc", [](uint8_t tmp) { return std::min<uint8_t>(tmp, 100); });
rhs.pwm_delay_ms = decode<decltype(rhs.pwm_delay_ms)>(node, "pwm_delay_ms");
rhs.switchoff_delay_ms = decode<decltype(rhs.switchoff_delay_ms)>(node, "switchoff_delay_ms");
return true;
}
bool decode_SafetyConfig(c4::yml::ConstNodeRef const& node, SafetyConfig& rhs) {
using ryml::ConstNodeRef;
if (node.invalid()) {
return false;
}
rhs.pp_mode = decode<decltype(rhs.pp_mode)>(node, "pp_mode");
rhs.cp_avg_ms = decode<decltype(rhs.cp_avg_ms)>(node, "cp_avg_ms", 10);
rhs.temperature_limit_pt1000_C =
decode<decltype(rhs.temperature_limit_pt1000_C)>(node, "temperature_limit_pt1000_C", 0);
rhs.inverted_emergency_input = decode<decltype(rhs.inverted_emergency_input)>(node, "inverted_emergency_input", 0);
rhs.relays[0] = decode<RelayConfig>(node, "relay_1");
rhs.relays[1] = decode<RelayConfig>(node, "relay_2");
rhs.relays[2] = decode<RelayConfig>(node, "relay_3");
return true;
}
bool decode_CbGpioConfig(c4::yml::ConstNodeRef const& node, CbGpioConfig& rhs) {
using ryml::ConstNodeRef;
if (node.invalid()) {
return false;
}
rhs.mode = decode<decltype(rhs.mode)>(node, "mode");
rhs.pulls = decode<decltype(rhs.pulls)>(node, "pulls");
rhs.strap_option_mdns_naming = decode_t(
node, "mdns", [](bool tmp) -> uint8_t { return tmp ? 1 : 0; }, std::make_optional<uint8_t>(0));
rhs.mode_config = decode<decltype(rhs.mode_config)>(node, "config", 0);
return true;
}
bool decode_CbUartConfig(c4::yml::ConstNodeRef const& node, CbUartConfig& rhs) {
using ryml::ConstNodeRef;
if (node.invalid()) {
return false;
}
rhs.baudrate = decode<decltype(rhs.baudrate)>(node, "baudrate");
rhs.stopbits = decode<decltype(rhs.stopbits)>(node, "stopbits");
rhs.parity = decode<decltype(rhs.parity)>(node, "parity");
return true;
}
bool decode_CbCanConfig(c4::yml::ConstNodeRef const& node, CbCanConfig& rhs) {
using ryml::ConstNodeRef;
if (node.invalid()) {
return false;
}
rhs.baudrate = decode<decltype(rhs.baudrate)>(node, "baudrate");
return true;
}
bool decode_CbNetworkConfig(c4::yml::ConstNodeRef const& node, CbNetworkConfig& rhs) {
using ryml::ConstNodeRef;
if (node.invalid()) {
return false;
}
ConstNodeRef local_node = node;
local_node = node.find_child("mdns_name");
if (not local_node.invalid()) {
auto limit = sizeof(rhs.mdns_name);
std::string name;
decode(local_node, name);
if (name.size() >= limit) {
return false;
}
if (name.size() >= limit) {
return false;
}
std::memset(rhs.mdns_name, 0, limit);
std::memcpy(rhs.mdns_name, name.c_str(), std::min(name.size(), limit));
return true;
}
throw yml_node_error(local_node);
return false;
}
namespace EXT_API = everest::lib::API;
namespace EXT_API_BSP = EXT_API::V1_0::types::evse_board_support;
bool decode_Connector_type(c4::yml::ConstNodeRef const& node, EXT_API_BSP::Connector_type& rhs) {
using ryml::ConstNodeRef;
if (node.invalid()) {
return false;
}
c4::csubstr value_view = node.val();
size_t total_size = 2 + value_view.size();
std::string quoted_value;
quoted_value.reserve(total_size);
quoted_value += "\"";
quoted_value.append(value_view.data(), value_view.size());
quoted_value += "\"";
return EXT_API::deserialize(quoted_value, rhs);
return true;
}
bool decode_HardwareCapabilities(c4::yml::ConstNodeRef const& node, EXT_API_BSP::HardwareCapabilities& rhs) {
using ryml::ConstNodeRef;
if (node.invalid()) {
return false;
}
rhs.max_current_A_import = decode<decltype(rhs.max_current_A_import)>(node, "max_current_A_import");
rhs.min_current_A_import = decode<decltype(rhs.min_current_A_import)>(node, "min_current_A_import");
rhs.max_phase_count_import = decode<decltype(rhs.max_phase_count_import)>(node, "max_phase_count_import");
rhs.min_phase_count_import = decode<decltype(rhs.min_phase_count_import)>(node, "min_phase_count_import");
rhs.max_current_A_export = decode<decltype(rhs.max_current_A_export)>(node, "max_current_A_export");
rhs.min_current_A_export = decode<decltype(rhs.min_current_A_export)>(node, "min_current_A_export");
rhs.max_phase_count_export = decode<decltype(rhs.max_phase_count_export)>(node, "max_phase_count_export");
rhs.min_phase_count_export = decode<decltype(rhs.min_phase_count_export)>(node, "min_phase_count_export");
rhs.supports_changing_phases_during_charging = decode<decltype(rhs.supports_changing_phases_during_charging)>(
node, "supports_changing_phases_during_charging");
rhs.supports_cp_state_E = decode<decltype(rhs.supports_cp_state_E)>(node, "supports_cp_state_E", false);
rhs.connector_type = decode<decltype(rhs.connector_type)>(node, "connector_type");
auto tmp = decode<float>(node, "max_plug_temperature_C", NAN);
rhs.max_plug_temperature_C = std::isnan(tmp) ? std::nullopt : std::make_optional<float>(tmp);
return true;
}
c4::yml::ConstNodeRef const& operator>>(c4::yml::ConstNodeRef const& node, CbGpioMode& rhs) {
if (decode_CbGpioMode(node, rhs)) {
return node;
}
throw std::runtime_error("CbGpioMode");
}
c4::yml::ConstNodeRef const& operator>>(c4::yml::ConstNodeRef const& node, CbGpioPulls& rhs) {
if (decode_CbGpioPulls(node, rhs)) {
return node;
}
throw std::runtime_error("CbGpioPulls");
}
c4::yml::ConstNodeRef const& operator>>(c4::yml::ConstNodeRef const& node, CbUartBaudrate& rhs) {
if (decode_CbUartBaudrate(node, rhs)) {
return node;
}
throw std::runtime_error("CbUartBaudrate");
}
c4::yml::ConstNodeRef const& operator>>(c4::yml::ConstNodeRef const& node, CbUartStopbits& rhs) {
if (decode_CbUartStopbits(node, rhs)) {
return node;
}
throw std::runtime_error("CbUartStopbits");
}
c4::yml::ConstNodeRef const& operator>>(c4::yml::ConstNodeRef const& node, CbUartParity& rhs) {
if (decode_CbUartParity(node, rhs)) {
return node;
}
throw std::runtime_error("CbUartParity");
}
c4::yml::ConstNodeRef const& operator>>(c4::yml::ConstNodeRef const& node, CbCanBaudrate& rhs) {
if (decode_CbCanBaudrate(node, rhs)) {
return node;
}
throw std::runtime_error("CbCanBaudrate");
}
c4::yml::ConstNodeRef const& operator>>(c4::yml::ConstNodeRef const& node, CbRelayMode& rhs) {
if (decode_CbRelayMode(node, rhs)) {
return node;
}
throw std::runtime_error("CbRelayMode");
}
c4::yml::ConstNodeRef const& operator>>(c4::yml::ConstNodeRef const& node, CbSafetyMode& rhs) {
if (decode_CbSafetyMode(node, rhs)) {
return node;
}
throw std::runtime_error("CbSafetyMode");
}
c4::yml::ConstNodeRef const& operator>>(c4::yml::ConstNodeRef const& node, RelayConfig& rhs) {
if (decode_RelayConfig(node, rhs)) {
return node;
}
throw std::runtime_error("RelayConfig");
}
c4::yml::ConstNodeRef const& operator>>(c4::yml::ConstNodeRef const& node, SafetyConfig& rhs) {
if (decode_SafetyConfig(node, rhs)) {
return node;
}
throw std::runtime_error("SafetyConfig");
}
c4::yml::ConstNodeRef const& operator>>(c4::yml::ConstNodeRef const& node, CbGpioConfig& rhs) {
if (decode_CbGpioConfig(node, rhs)) {
return node;
}
throw std::runtime_error("CbGpioConfig");
}
c4::yml::ConstNodeRef const& operator>>(c4::yml::ConstNodeRef const& node, CbUartConfig& rhs) {
if (decode_CbUartConfig(node, rhs)) {
return node;
}
throw std::runtime_error("CbUartConfig");
}
c4::yml::ConstNodeRef const& operator>>(c4::yml::ConstNodeRef const& node, CbCanConfig& rhs) {
if (decode_CbCanConfig(node, rhs)) {
return node;
}
throw std::runtime_error("CbCanConfig");
}
c4::yml::ConstNodeRef const& operator>>(c4::yml::ConstNodeRef const& node, CbNetworkConfig& rhs) {
if (decode_CbNetworkConfig(node, rhs)) {
return node;
}
throw std::runtime_error("CbNetworkConfig");
}
c4::yml::ConstNodeRef const& operator>>(c4::yml::ConstNodeRef const& node, EXT_API_BSP::Connector_type& rhs) {
if (decode_Connector_type(node, rhs)) {
return node;
}
throw std::runtime_error("type");
}
c4::yml::ConstNodeRef const& operator>>(c4::yml::ConstNodeRef const& node, EXT_API_BSP::HardwareCapabilities& rhs) {
if (decode_HardwareCapabilities(node, rhs)) {
return node;
}
throw std::runtime_error("HardwareCapabilities");
}
} // namespace charge_bridge::utilities