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

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

512 lines
19 KiB
C++

// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
#include "BUEvseBoardSupport.hpp"
#include "ftxui/component/component.hpp"
#include "ftxui/component/screen_interactive.hpp"
#include "ftxui/dom/table.hpp"
#include "generated/types/evse_manager.hpp"
using namespace ftxui;
namespace module {
static std::vector<std::vector<std::string>> to_string(types::evse_board_support::HardwareCapabilities c) {
std::vector<std::vector<std::string>> hw_caps;
hw_caps.push_back(
{"Max/Min Current", fmt::format("Imp: {}A/{}A Exp: {}A/{}A", c.max_current_A_import, c.min_current_A_import,
c.max_current_A_export, c.min_current_A_export)});
hw_caps.push_back({"Max/Min Phase Count",
fmt::format("Imp: {}ph/{}ph Exp: {}ph/{}ph", c.max_phase_count_import, c.min_phase_count_import,
c.max_phase_count_export, c.min_phase_count_export)});
hw_caps.push_back({"Connector Type", types::evse_board_support::connector_type_to_string(c.connector_type)});
hw_caps.push_back({"CP State E Support", c.supports_cp_state_E ? "Yes" : "No"});
if (c.max_plug_temperature_C.has_value()) {
hw_caps.push_back({"Max plug temperature", fmt::format("{}C", c.max_plug_temperature_C.value())});
}
return hw_caps;
}
void BUEvseBoardSupport::init() {
}
void BUEvseBoardSupport::ready() {
auto screen = ScreenInteractive::Fullscreen();
r_bsp->subscribe_event([this, &screen](const types::board_support_common::BspEvent e) {
{
std::scoped_lock lock(data_mutex);
switch (e.event) {
case types::board_support_common::Event::A:
cp_state = "A";
break;
case types::board_support_common::Event::B:
cp_state = "B";
break;
case types::board_support_common::Event::C:
cp_state = "C";
break;
case types::board_support_common::Event::D:
cp_state = "D";
break;
case types::board_support_common::Event::E:
cp_state = "E";
break;
case types::board_support_common::Event::F:
cp_state = "F";
break;
case types::board_support_common::Event::PowerOn:
relais_feedback = fmt::format("PowerOn (after {} ms)",
std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - last_allow_power_on_time_point)
.count());
break;
case types::board_support_common::Event::PowerOff:
relais_feedback = fmt::format("PowerOff (after {} ms)",
std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - last_allow_power_on_time_point)
.count());
break;
}
}
screen.Post(Event::Custom);
});
r_bsp->subscribe_capabilities([this, &screen](const types::evse_board_support::HardwareCapabilities c) {
{
std::scoped_lock lock(data_mutex);
hw_caps = to_string(c);
}
screen.Post(Event::Custom);
});
r_bsp->subscribe_telemetry([this, &screen](const types::evse_board_support::Telemetry t) {
{
std::scoped_lock lock(data_mutex);
telemetry = fmt::format("EVSE {}C Fan {}rpm Supply {}V/{}V, Relais {}", t.evse_temperature_C, t.fan_rpm,
t.supply_voltage_12V, t.supply_voltage_minus_12V, t.relais_on);
if (t.plug_temperature_C.has_value()) {
telemetry = telemetry += fmt::format(" Plug {}C ", t.plug_temperature_C.value());
}
}
screen.Post(Event::Custom);
});
r_bsp->subscribe_ac_pp_ampacity([this, &screen](const types::board_support_common::ProximityPilot pp) {
{
std::scoped_lock lock(data_mutex);
proximity_pilot = types::board_support_common::ampacity_to_string(pp.ampacity);
}
screen.Post(Event::Custom);
});
r_bsp->subscribe_request_stop_transaction([this, &screen](const types::evse_manager::StopTransactionRequest t) {
{
std::scoped_lock lock(data_mutex);
stop_transaction = stop_transaction_reason_to_string(t.reason);
}
screen.Post(Event::Custom);
});
// telemetry, pp (needs to be requested as well)
auto error_handler = [this, &screen](const Everest::error::Error& error) {
{
std::scoped_lock lock(this->data_mutex);
this->last_error_raised = fmt::format("Fault raised: {}, {}", error.type, error.sub_type);
}
screen.Post(Event::Custom);
};
auto error_cleared_handler = [this, &screen](const Everest::error::Error& error) {
{
std::scoped_lock lock(this->data_mutex);
this->last_error_cleared = fmt::format("Fault cleared: {}, {}", error.type, error.sub_type);
}
screen.Post(Event::Custom);
};
r_bsp->subscribe_all_errors(error_handler, error_cleared_handler);
if (!r_ac_rcd.empty()) {
r_ac_rcd.at(0)->subscribe_all_errors(error_handler, error_cleared_handler);
}
if (!r_lock_motor.empty()) {
r_lock_motor.at(0)->subscribe_all_errors(error_handler, error_cleared_handler);
}
std::string last_command = "None";
// ---------------------------------------------------------------------------
// Relais allow power on
// ---------------------------------------------------------------------------
Component relais_on_button = Button(
"Allow power on",
[&] {
last_command = "Allow power on";
last_allow_power_on_time_point = std::chrono::steady_clock::now();
r_bsp->call_allow_power_on({true, types::evse_board_support::Reason::FullPowerCharging});
},
ButtonOption::Animated(Color::Blue, Color::White, Color::BlueLight, Color::White));
Component relais_off_button = Button(
"Force power off",
[&] {
last_command = "Force power off";
last_allow_power_on_time_point = std::chrono::steady_clock::now();
r_bsp->call_allow_power_on({false, types::evse_board_support::Reason::PowerOff});
},
ButtonOption::Animated(Color::Red, Color::White, Color::RedLight, Color::White));
auto relais_component = Container::Horizontal({
Container::Vertical({
relais_on_button,
relais_off_button,
}),
});
auto relais_renderer = Renderer(relais_component, [&] {
auto relais_win = window(text("Relais"), relais_component->Render());
return vbox({
hbox({
relais_win,
}),
}) |
flex_grow;
});
// ---------------------------------------------------------------------------
// PWM control
// ---------------------------------------------------------------------------
std::string pwm_duty_cycle_str{"5.0"};
Component cp_state_X1_button = Button(
"CP State X1 (PWM Off)",
[&] {
last_command = "CP State X1 (PWM Off)";
r_bsp->call_cp_state_X1();
},
ButtonOption::Animated(Color::Blue, Color::White, Color::BlueLight, Color::White));
Component pwm_on_button = Button(
"PWM On",
[&] {
if (pwm_duty_cycle_str.empty())
pwm_duty_cycle_str = "5.0";
last_command = "PWM On (" + pwm_duty_cycle_str + ")";
r_bsp->call_pwm_on(std::stof(pwm_duty_cycle_str.c_str()));
},
ButtonOption::Animated(Color::Green, Color::White, Color::GreenLight, Color::White));
Component cp_state_E_button = Button(
"CP State E",
[&] {
last_command = "CP State E";
r_bsp->call_cp_state_E();
},
ButtonOption::Animated(Color::Magenta, Color::White, Color::MagentaLight, Color::White));
Component cp_state_F_button = Button(
"CP State F",
[&] {
last_command = "CP State F";
r_bsp->call_cp_state_F();
},
ButtonOption::Animated(Color::Red, Color::White, Color::RedLight, Color::White));
InputOption o;
o.multiline = false;
o.cursor_position = 0;
o.on_enter = [&]() {
last_command = "Update PWM DC to " + pwm_duty_cycle_str;
r_bsp->call_pwm_on(std::stof(pwm_duty_cycle_str.c_str()));
};
auto pwm_dc = Input(&pwm_duty_cycle_str, "5.0", o);
auto pwm_component = Container::Horizontal({
Container::Vertical({
cp_state_E_button,
cp_state_F_button,
cp_state_X1_button,
pwm_on_button,
pwm_dc,
}),
});
auto pwm_renderer = Renderer(pwm_component, [&] {
auto pwm_win = window(text("PWM"), pwm_component->Render());
return vbox({
hbox({
pwm_win,
}),
}) |
flex_grow;
});
// ---------------------------------------------------------------------------
// Enable/Disable
// ---------------------------------------------------------------------------
Component enable_on_button = Button(
"Enable",
[&] {
last_command = "Enable";
r_bsp->call_enable(true);
},
ButtonOption::Animated(Color::Blue, Color::White, Color::BlueLight, Color::White));
Component enable_off_button = Button(
"Disable",
[&] {
last_command = "Disable";
r_bsp->call_enable(false);
},
ButtonOption::Animated(Color::Red, Color::White, Color::RedLight, Color::White));
auto enable_component = Container::Vertical({
enable_on_button,
enable_off_button,
});
auto enable_renderer =
Renderer(enable_component, [&] { return window(text("Enable"), enable_component->Render()); });
// ---------------------------------------------------------------------------
// AC switch phases while charging
// ---------------------------------------------------------------------------
Component three_phase_button = Button(
"ThreePhases",
[&] {
last_command = "Switch ThreePhases";
r_bsp->call_ac_switch_three_phases_while_charging(true);
},
ButtonOption::Animated(Color::Blue, Color::White, Color::BlueLight, Color::White));
Component single_phase_button = Button(
"SinglePhase",
[&] {
last_command = "Switch SinglePhase";
r_bsp->call_ac_switch_three_phases_while_charging(false);
},
ButtonOption::Animated(Color::Red, Color::White, Color::RedLight, Color::White));
auto phase_switch_component = Container::Vertical({
three_phase_button,
single_phase_button,
});
auto phase_switch_renderer = Renderer(
phase_switch_component, [&] { return window(text("AC Phase Switch"), phase_switch_component->Render()); });
// ---------------------------------------------------------------------------
// AC Overcurrent Limit command
// ---------------------------------------------------------------------------
std::string ac_oc_limit_str = "16.0"; // Default current limit (A)
Component ac_oc_limit_button = Button(
"Set Limit",
[&] {
if (ac_oc_limit_str.empty())
ac_oc_limit_str = "16.0";
last_command = "Set AC Overcurrent Limit (" + ac_oc_limit_str + " A)";
r_bsp->call_ac_set_overcurrent_limit_A(std::stof(ac_oc_limit_str.c_str()));
},
ButtonOption::Animated(Color::Blue, Color::White, Color::BlueLight, Color::White));
InputOption ac_oc_input_opt;
ac_oc_input_opt.multiline = false;
ac_oc_input_opt.cursor_position = 0;
ac_oc_input_opt.on_enter = [&]() {
if (ac_oc_limit_str.empty())
ac_oc_limit_str = "16.0";
last_command = "Set AC Overcurrent Limit (" + ac_oc_limit_str + " A)";
r_bsp->call_ac_set_overcurrent_limit_A(std::stof(ac_oc_limit_str.c_str()));
};
auto ac_oc_input = Input(&ac_oc_limit_str, "16.0", ac_oc_input_opt);
auto ac_oc_component = Container::Vertical({
ac_oc_limit_button,
ac_oc_input,
});
auto ac_oc_renderer =
Renderer(ac_oc_component, [&] { return window(text("AC Overcurrent Limit"), ac_oc_component->Render()); });
// ---------------------------------------------------------------------------
// Vars
// ---------------------------------------------------------------------------
auto vars_renderer = Renderer([&] {
std::vector<std::vector<std::string>> table_content;
{
std::scoped_lock lock(data_mutex);
table_content = {{"CP State", cp_state},
{"Relais", relais_feedback},
{"Telemetry", telemetry},
{"Proximity Pilot", proximity_pilot}};
}
auto vars = Table(table_content);
vars.SelectColumn(0).Border(LIGHT);
for (int i = 0; i < table_content.size(); i++) {
vars.SelectRow(i).Border(LIGHT);
}
return vbox({
hbox({
window(
text("Information"),
vbox({text("Last command: " + last_command), text("Last error raised: " + last_error_raised),
text("Last error cleared: " + last_error_cleared), vars.Render()})),
}),
}) |
flex_grow;
});
// ---------------------------------------------------------------------------
// Request stop transaction
// ---------------------------------------------------------------------------
auto vars_stop_transaction_renderer = Renderer([&] {
Element reason = text("Request stop transaction: " + stop_transaction);
return vbox({
hbox({
window(text("Buttons"), reason),
}),
}) |
flex_grow;
});
// ---------------------------------------------------------------------------
// Capabilities
// ---------------------------------------------------------------------------
types::evse_board_support::HardwareCapabilities dummy_caps;
dummy_caps.connector_type = types::evse_board_support::Connector_type::IEC62196Type2Cable;
hw_caps = to_string(dummy_caps);
auto caps_renderer = Renderer([&] {
std::vector<std::vector<std::string>> caps_table_content;
{
std::scoped_lock lock(data_mutex);
caps_table_content = hw_caps;
}
auto caps = Table(caps_table_content);
caps.SelectColumn(0).Border(LIGHT);
for (int i = 0; i < caps_table_content.size(); i++) {
caps.SelectRow(i).Border(LIGHT);
}
return vbox({
hbox({
window(text("Capabilities"), caps.Render()),
}),
}) |
flex_grow;
});
// ---------------------------------------------------------------------------
// AC RCD
// ---------------------------------------------------------------------------
auto no_rcd_text = Renderer([] { return text("No AC RCD module connected in config."); });
auto rcd_component = Container::Vertical({
no_rcd_text,
});
if (!r_ac_rcd.empty()) {
std::string rcd_current_display = "N/A";
std::string rcd_reset_result = "";
Component rcd_self_test_button = Button(
"Self Test",
[&] {
last_command = "AC RCD Self Test";
r_ac_rcd.at(0)->call_self_test();
},
ButtonOption::Animated(Color::Green, Color::White, Color::GreenLight, Color::White));
Component rcd_reset_button = Button(
"Reset",
[&] {
last_command = "AC RCD Reset";
bool reset_result = r_ac_rcd.at(0)->call_reset();
rcd_reset_result = fmt::format("Reset result: {}", reset_result ? "Success" : "Failed");
},
ButtonOption::Animated(Color::Blue, Color::White, Color::BlueLight, Color::White));
rcd_component = Container::Vertical({
rcd_self_test_button,
rcd_reset_button,
});
}
// ---------------------------------------------------------------------------
// Connector Lock
// ---------------------------------------------------------------------------
auto no_lock_text = Renderer([] { return text("No Connector Lock module connected in config."); });
auto lock_component = Container::Vertical({
no_lock_text,
});
if (!r_lock_motor.empty()) {
Component lock_button = Button(
"Connector Lock",
[&] {
last_command = "Connector Lock";
r_lock_motor.at(0)->call_lock();
},
ButtonOption::Animated(Color::Red, Color::White, Color::RedLight, Color::White));
Component unlock_button = Button(
"Connector Unlock",
[&] {
last_command = "Connector Unlock";
r_lock_motor.at(0)->call_unlock();
},
ButtonOption::Animated(Color::Blue, Color::White, Color::BlueLight, Color::White));
lock_component = Container::Vertical({
lock_button,
unlock_button,
});
}
// ---------------------------------------------------------------------------
// Main layout
// ---------------------------------------------------------------------------
auto command_row =
Container::Horizontal({pwm_renderer, relais_renderer, enable_renderer, phase_switch_renderer, ac_oc_renderer});
auto info_row = Container::Horizontal({
vars_renderer,
caps_renderer,
vars_stop_transaction_renderer,
});
// Add RCD and Lock components to the interactive container tree
auto main_container = Container::Vertical({info_row, command_row, rcd_component, lock_component});
auto main_renderer = Renderer(main_container, [&] {
return vbox(text("EVSE Board Support") | bold | hcenter, separator(), info_row->Render(), command_row->Render(),
separator(),
hbox(window(text("AC RCD"), vbox(text("RCD Current: " + rcd_current_display + " mA"), separator(),
rcd_component->Render(), text(rcd_reset_result))) |
flex,
separator(), window(text("Connector Lock"), lock_component->Render()) | flex));
});
screen.Loop(main_renderer);
}
} // namespace module