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:
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user