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,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