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:
326
tools/EVerest-main/modules/BringUp/BUPowermeter/BUPowermeter.cpp
Normal file
326
tools/EVerest-main/modules/BringUp/BUPowermeter/BUPowermeter.cpp
Normal file
@@ -0,0 +1,326 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include "BUPowermeter.hpp"
|
||||
#include "ftxui/component/component.hpp"
|
||||
#include "ftxui/component/screen_interactive.hpp"
|
||||
#include "ftxui/dom/table.hpp"
|
||||
#include <ranges>
|
||||
|
||||
using namespace ftxui;
|
||||
|
||||
namespace module {
|
||||
|
||||
void optional_add(std::vector<std::vector<std::string>>& table, std::string text, std::string s_value) {
|
||||
table.push_back({text, s_value});
|
||||
}
|
||||
|
||||
void optional_add(std::vector<std::vector<std::string>>& table, std::string text, std::optional<float> o_value) {
|
||||
if (o_value.has_value()) {
|
||||
table.push_back({text, std::to_string(o_value.value())});
|
||||
}
|
||||
}
|
||||
|
||||
void optional_add(std::vector<std::vector<std::string>>& table, std::string text, std::optional<std::string> o_value) {
|
||||
if (o_value.has_value()) {
|
||||
table.push_back({text, o_value.value()});
|
||||
}
|
||||
}
|
||||
|
||||
void BUPowermeter::init() {
|
||||
}
|
||||
|
||||
// Helper function to wrap long text into multiple lines
|
||||
std::vector<std::string> WrapText(const std::string& text, size_t max_width) {
|
||||
std::vector<std::string> lines;
|
||||
size_t start = 0;
|
||||
|
||||
if (max_width < text.size()) {
|
||||
while (start < text.size()) {
|
||||
size_t end = std::min(start + max_width, text.size());
|
||||
size_t last_space = text.rfind(' ', end);
|
||||
|
||||
// If there's no space, split at max_width
|
||||
if (last_space == std::string::npos || last_space < start) {
|
||||
lines.push_back(text.substr(start, max_width));
|
||||
start += max_width;
|
||||
} else {
|
||||
lines.push_back(text.substr(start, last_space - start));
|
||||
start = last_space + 1; // Skip the space
|
||||
}
|
||||
}
|
||||
} else {
|
||||
lines.push_back(text);
|
||||
}
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
// Helper function to transform text strings into FTXUI Elements
|
||||
std::vector<Element> WrapTextToElements(const std::vector<std::string>& strings) {
|
||||
std::vector<Element> elements;
|
||||
elements.reserve(strings.size()); // Preallocate memory
|
||||
|
||||
// Use std::transform to convert strings to FTXUI elements
|
||||
std::transform(strings.begin(), strings.end(), std::back_inserter(elements),
|
||||
[](const std::string& line) { return text(line); } // Convert each string to FTXUI text
|
||||
);
|
||||
|
||||
return elements;
|
||||
}
|
||||
|
||||
// Helper function to format and render the table content
|
||||
std::vector<std::vector<Element>> FormatTableContent(const std::vector<std::vector<std::string>>& raw_table_content,
|
||||
size_t max_width) {
|
||||
std::vector<std::vector<Element>> table_elements;
|
||||
|
||||
for (const auto& row : raw_table_content) {
|
||||
std::vector<Element> formatted_row;
|
||||
|
||||
for (const auto& cell : row) {
|
||||
// Wrap long strings into multiple lines
|
||||
std::vector<std::string> wrapped_lines = WrapText(cell, max_width);
|
||||
|
||||
// Convert wrapped lines into FTXUI text elements
|
||||
auto cell_element = vbox(WrapTextToElements(wrapped_lines));
|
||||
|
||||
formatted_row.push_back(cell_element);
|
||||
}
|
||||
|
||||
table_elements.push_back(std::move(formatted_row));
|
||||
}
|
||||
|
||||
return table_elements;
|
||||
}
|
||||
|
||||
void BUPowermeter::ready() {
|
||||
auto screen = ScreenInteractive::Fullscreen();
|
||||
|
||||
r_powermeter->subscribe_public_key_ocmf([this, &screen](std::string pk) {
|
||||
public_key = pk;
|
||||
last_event_received = event_received;
|
||||
event_received = std::chrono::steady_clock::now();
|
||||
screen.Post(Event::Custom);
|
||||
});
|
||||
|
||||
r_powermeter->subscribe_powermeter([this, &screen](types::powermeter::Powermeter pm) {
|
||||
powermeter = pm;
|
||||
last_event_received = event_received;
|
||||
event_received = std::chrono::steady_clock::now();
|
||||
screen.Post(Event::Custom);
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Commands that can be sent over Powermeter interface
|
||||
// ---------------------------------------------------------------------------
|
||||
Component start_transaction_button = Button(
|
||||
"Start transaction",
|
||||
[&] {
|
||||
types::powermeter::OCMFUserIdentificationStatus
|
||||
identification_status; ///< OCMF Identification Status (IS): General status for user assignment
|
||||
std::vector<types::powermeter::OCMFIdentificationFlags>
|
||||
identification_flags; ///< OCMF Identification Flags (IF): Detailed statements about the user
|
||||
///< assignment, represented by one or more identifiers
|
||||
types::powermeter::OCMFIdentificationType
|
||||
identification_type; ///< OCMF Identification Type (IT): Type of identification data
|
||||
std::optional<types::powermeter::OCMFIdentificationLevel>
|
||||
identification_level; ///< OCMF Identification Level (IL): Encoded overall status of the user assignment
|
||||
std::optional<std::string>
|
||||
identification_data; ///< OCMF Identification Data (ID): The actual identification data e.g. a hex-coded
|
||||
///< UID according to ISO 14443.
|
||||
std::optional<std::string> tariff_text; ///< Tariff text
|
||||
|
||||
last_command = "Start transaction";
|
||||
last_command_duration = "";
|
||||
screen.Post(Event::Custom);
|
||||
types::powermeter::TransactionReq tr;
|
||||
tr.evse_id = config.evse_id;
|
||||
tr.transaction_id = config.transaction_id;
|
||||
tr.identification_status = types::powermeter::OCMFUserIdentificationStatus::ASSIGNED;
|
||||
tr.identification_type = types::powermeter::OCMFIdentificationType::ISO14443;
|
||||
tr.identification_data = config.identification_data;
|
||||
tr.tariff_text = config.tariff_text;
|
||||
|
||||
auto now_b = std::chrono::steady_clock::now();
|
||||
tr_start = r_powermeter->call_start_transaction(tr);
|
||||
auto now_e = std::chrono::steady_clock::now();
|
||||
last_command_duration =
|
||||
fmt::format("{} ms", std::chrono::duration_cast<std::chrono::milliseconds>(now_e - now_b).count());
|
||||
},
|
||||
ButtonOption::Animated(Color::Blue, Color::White, Color::RedLight, Color::White));
|
||||
|
||||
Component stop_transaction_button = Button(
|
||||
"Stop transaction",
|
||||
[&] {
|
||||
last_command = "Stop transaction";
|
||||
std::string transaction_id = config.transaction_id;
|
||||
auto now_b = std::chrono::steady_clock::now();
|
||||
tr_stop = r_powermeter->call_stop_transaction(transaction_id);
|
||||
auto now_e = std::chrono::steady_clock::now();
|
||||
last_command_duration =
|
||||
fmt::format("{} ms", std::chrono::duration_cast<std::chrono::milliseconds>(now_e - now_b).count());
|
||||
},
|
||||
ButtonOption::Animated(Color::Blue, Color::White, Color::RedLight, Color::White));
|
||||
|
||||
Component stop_transaction0_button = Button(
|
||||
"Stop transaction empty",
|
||||
[&] {
|
||||
last_command = "Stop transaction";
|
||||
std::string transaction_id = "";
|
||||
auto now_b = std::chrono::steady_clock::now();
|
||||
tr_stop = r_powermeter->call_stop_transaction(transaction_id);
|
||||
auto now_e = std::chrono::steady_clock::now();
|
||||
last_command_duration =
|
||||
fmt::format("{} ms", std::chrono::duration_cast<std::chrono::milliseconds>(now_e - now_b).count());
|
||||
},
|
||||
ButtonOption::Animated(Color::Blue, Color::White, Color::RedLight, Color::White));
|
||||
|
||||
Component stop_transaction1_button = Button(
|
||||
"Stop transaction dummy",
|
||||
[&] {
|
||||
last_command = "Stop transaction";
|
||||
std::string transaction_id = "DUMMY";
|
||||
auto now_b = std::chrono::steady_clock::now();
|
||||
tr_stop = r_powermeter->call_stop_transaction(transaction_id);
|
||||
auto now_e = std::chrono::steady_clock::now();
|
||||
last_command_duration =
|
||||
fmt::format("{} ms", std::chrono::duration_cast<std::chrono::milliseconds>(now_e - now_b).count());
|
||||
},
|
||||
ButtonOption::Animated(Color::Blue, Color::White, Color::RedLight, Color::White));
|
||||
|
||||
auto cmds_component = Container::Horizontal({
|
||||
start_transaction_button,
|
||||
stop_transaction_button,
|
||||
stop_transaction0_button,
|
||||
stop_transaction1_button,
|
||||
});
|
||||
|
||||
auto cmds_renderer = Renderer(cmds_component, [&] {
|
||||
auto cmds_win = window(text("Commands"), cmds_component->Render());
|
||||
return vbox({
|
||||
hbox({cmds_win}) | center,
|
||||
separator(),
|
||||
}) |
|
||||
flex_grow;
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Vars
|
||||
// ---------------------------------------------------------------------------
|
||||
auto vars_renderer = Renderer([&] {
|
||||
std::vector<std::vector<std::string>> table_content;
|
||||
auto last_event_duration = fmt::format(
|
||||
"{} ms",
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(event_received - last_event_received).count());
|
||||
types::units::Energy ed = {};
|
||||
types::units::Power pd = {};
|
||||
types::units::Voltage vd = {};
|
||||
types::units_signed::SignedMeterValue svd = {};
|
||||
types::units::Current cd = {};
|
||||
types::units::Frequency fd = {};
|
||||
|
||||
optional_add(table_content, "Transaction start response: status",
|
||||
types::powermeter::transaction_request_status_to_string(tr_start.status));
|
||||
optional_add(table_content, "Transaction start response: error", tr_start.error);
|
||||
optional_add(table_content, "Transaction start response: transaction_min_stop_time",
|
||||
tr_start.transaction_min_stop_time);
|
||||
optional_add(table_content, "Transaction start response: transaction_max_stop_time",
|
||||
tr_start.transaction_max_stop_time);
|
||||
optional_add(table_content, "Transaction stop response: status",
|
||||
types::powermeter::transaction_request_status_to_string(tr_stop.status));
|
||||
optional_add(table_content, "Transaction stop response: error", tr_stop.error);
|
||||
optional_add(table_content, "Transaction stop response: signed_meter_value",
|
||||
tr_stop.signed_meter_value.value_or(svd).signed_meter_data);
|
||||
if (powermeter.signed_meter_value.has_value()) {
|
||||
optional_add(table_content, "Powermeter: signed meter value",
|
||||
powermeter.signed_meter_value.value().signed_meter_data);
|
||||
optional_add(table_content, "Powermeter: signing method",
|
||||
powermeter.signed_meter_value.value().signing_method);
|
||||
optional_add(table_content, "Powermeter: encoding method",
|
||||
powermeter.signed_meter_value.value().encoding_method);
|
||||
optional_add(table_content, "Powermeter: public key", powermeter.signed_meter_value.value().public_key);
|
||||
}
|
||||
optional_add(table_content, "Powermeter: time stamp", powermeter.timestamp);
|
||||
if (powermeter.temperatures.has_value()) {
|
||||
int i = 1;
|
||||
for (const auto& sensor : *powermeter.temperatures) {
|
||||
std::string label = "Temperature sensor" + std::to_string(i++);
|
||||
optional_add(table_content, label, sensor.temperature);
|
||||
}
|
||||
}
|
||||
|
||||
optional_add(table_content, "Powermeter: imported energy in Wh (from grid), total",
|
||||
std::to_string(powermeter.energy_Wh_import.total));
|
||||
optional_add(table_content, "Powermeter: imported energy in Wh (from grid): L1",
|
||||
powermeter.energy_Wh_import.L1);
|
||||
optional_add(table_content, "Powermeter: imported energy in Wh (from grid): L2",
|
||||
powermeter.energy_Wh_import.L2);
|
||||
optional_add(table_content, "Powermeter: imported energy in Wh (from grid): L3",
|
||||
powermeter.energy_Wh_import.L3);
|
||||
|
||||
optional_add(table_content, "Powermeter: exported energy in Wh (to grid), total",
|
||||
std::to_string(powermeter.energy_Wh_export.value_or(ed).total));
|
||||
optional_add(table_content, "Powermeter: exported energy in Wh (to grid): L1",
|
||||
powermeter.energy_Wh_export.value_or(ed).L1);
|
||||
optional_add(table_content, "Powermeter: exported energy in Wh (to grid): L2",
|
||||
powermeter.energy_Wh_export.value_or(ed).L2);
|
||||
optional_add(table_content, "Powermeter: exported energy in Wh (to grid): L3",
|
||||
powermeter.energy_Wh_export.value_or(ed).L3);
|
||||
|
||||
optional_add(table_content, "Powermeter: user defined meter ID", powermeter.meter_id);
|
||||
optional_add(table_content, "Powermeter: 3 phase rotation error (ccw)", powermeter.phase_seq_error);
|
||||
|
||||
optional_add(table_content, "Powermeter: voltage in V, DC", powermeter.voltage_V.value_or(vd).DC);
|
||||
optional_add(table_content, "Powermeter: voltage in V: L1", powermeter.voltage_V.value_or(vd).L1);
|
||||
optional_add(table_content, "Powermeter: voltage in V: L2", powermeter.voltage_V.value_or(vd).L2);
|
||||
optional_add(table_content, "Powermeter: voltage in V: L3", powermeter.voltage_V.value_or(vd).L3);
|
||||
optional_add(table_content, "Powermeter: current in A, DC", powermeter.current_A.value_or(cd).DC);
|
||||
optional_add(table_content, "Powermeter: current in A: L1", powermeter.current_A.value_or(cd).L1);
|
||||
optional_add(table_content, "Powermeter: current in A: L2", powermeter.current_A.value_or(cd).L2);
|
||||
optional_add(table_content, "Powermeter: current in A: L3", powermeter.current_A.value_or(cd).L3);
|
||||
optional_add(table_content, "Powermeter: frequency in Hz: L1", powermeter.frequency_Hz.value_or(fd).L1);
|
||||
optional_add(table_content, "Powermeter: frequency in Hz: L2", powermeter.frequency_Hz.value_or(fd).L2);
|
||||
optional_add(table_content, "Powermeter: frequency in Hz: L3", powermeter.frequency_Hz.value_or(fd).L3);
|
||||
int i = 1;
|
||||
for (const auto& sensor : *powermeter.temperatures) {
|
||||
std::string label = "Temperature sensor" + std::to_string(i++);
|
||||
optional_add(table_content, label, sensor.temperature);
|
||||
}
|
||||
optional_add(table_content, "Public key", public_key);
|
||||
|
||||
size_t max_width = 120;
|
||||
auto formatted_content = FormatTableContent(table_content, max_width);
|
||||
auto vars = Table(std::move(formatted_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 command duration: " + last_command_duration),
|
||||
text("Time between events: " + last_event_duration), vars.Render()})),
|
||||
}) | center,
|
||||
}) |
|
||||
flex_grow;
|
||||
});
|
||||
|
||||
auto main_container = Container::Vertical({
|
||||
cmds_renderer,
|
||||
vars_renderer,
|
||||
});
|
||||
|
||||
auto main_renderer = Renderer(main_container, [&] {
|
||||
return vbox({
|
||||
text("Powermeter Component") | bold | hcenter,
|
||||
main_container->Render(),
|
||||
});
|
||||
});
|
||||
|
||||
screen.Loop(main_renderer);
|
||||
}
|
||||
|
||||
} // namespace module
|
||||
@@ -0,0 +1,71 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
|
||||
#ifndef BUPOWERMETER_HPP
|
||||
#define BUPOWERMETER_HPP
|
||||
|
||||
//
|
||||
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
|
||||
// template version 2
|
||||
//
|
||||
|
||||
#include "ld-ev.hpp"
|
||||
|
||||
// headers for required interface implementations
|
||||
#include <generated/interfaces/powermeter/Interface.hpp>
|
||||
|
||||
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
|
||||
// insert your custom include headers here
|
||||
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
|
||||
|
||||
namespace module {
|
||||
|
||||
struct Conf {
|
||||
std::string evse_id;
|
||||
std::string transaction_id;
|
||||
std::string identification_data;
|
||||
std::string tariff_text;
|
||||
};
|
||||
|
||||
class BUPowermeter : public Everest::ModuleBase {
|
||||
public:
|
||||
BUPowermeter() = delete;
|
||||
BUPowermeter(const ModuleInfo& info, std::unique_ptr<powermeterIntf> r_powermeter, Conf& config) :
|
||||
ModuleBase(info), r_powermeter(std::move(r_powermeter)), config(config){};
|
||||
|
||||
const std::unique_ptr<powermeterIntf> r_powermeter;
|
||||
const Conf& config;
|
||||
|
||||
// ev@1fce4c5e-0ab8-41bb-90f7-14277703d2ac:v1
|
||||
// insert your public definitions here
|
||||
// ev@1fce4c5e-0ab8-41bb-90f7-14277703d2ac:v1
|
||||
|
||||
protected:
|
||||
// ev@4714b2ab-a24f-4b95-ab81-36439e1478de:v1
|
||||
// insert your protected definitions here
|
||||
// ev@4714b2ab-a24f-4b95-ab81-36439e1478de:v1
|
||||
|
||||
private:
|
||||
friend class LdEverest;
|
||||
void init();
|
||||
void ready();
|
||||
|
||||
// ev@211cfdbe-f69a-4cd6-a4ec-f8aaa3d1b6c8:v1
|
||||
// insert your private definitions here
|
||||
std::string last_command;
|
||||
std::string last_command_duration;
|
||||
std::chrono::time_point<std::chrono::steady_clock> last_event_received;
|
||||
std::chrono::time_point<std::chrono::steady_clock> event_received;
|
||||
std::string public_key;
|
||||
types::powermeter::Powermeter powermeter;
|
||||
types::powermeter::TransactionStartResponse tr_start;
|
||||
types::powermeter::TransactionStopResponse tr_stop;
|
||||
// ev@211cfdbe-f69a-4cd6-a4ec-f8aaa3d1b6c8:v1
|
||||
};
|
||||
|
||||
// ev@087e516b-124c-48df-94fb-109508c7cda9:v1
|
||||
// insert other definitions here
|
||||
// ev@087e516b-124c-48df-94fb-109508c7cda9:v1
|
||||
|
||||
} // namespace module
|
||||
|
||||
#endif // BUPOWERMETER_HPP
|
||||
@@ -0,0 +1,21 @@
|
||||
#
|
||||
# AUTO GENERATED - MARKED REGIONS WILL BE KEPT
|
||||
# template version 3
|
||||
#
|
||||
|
||||
# module setup:
|
||||
# - ${MODULE_NAME}: module name
|
||||
ev_setup_cpp_module()
|
||||
|
||||
# ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1
|
||||
# insert your custom targets and additional config variables here
|
||||
target_link_libraries(${MODULE_NAME}
|
||||
PRIVATE ftxui::screen
|
||||
PRIVATE ftxui::dom
|
||||
PRIVATE ftxui::component
|
||||
)
|
||||
# ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1
|
||||
|
||||
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1
|
||||
# insert other things like install cmds etc here
|
||||
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1
|
||||
@@ -0,0 +1,25 @@
|
||||
description: Interactive bring up helper for Powermeter interface
|
||||
config:
|
||||
evse_id:
|
||||
description: "Id of this EVSE"
|
||||
type: string
|
||||
default: "EVSE_ID"
|
||||
transaction_id:
|
||||
description: "OCPP transaction UUID"
|
||||
type: string
|
||||
default: "12345678-1234-5678-1234-567812345678"
|
||||
identification_data:
|
||||
description: "User identification means, e.g. RFID card ID"
|
||||
type: string
|
||||
default: "A1B2C3D4E5F6"
|
||||
tariff_text:
|
||||
description: "Tariff Text"
|
||||
type: string
|
||||
default: "custom"
|
||||
requires:
|
||||
powermeter:
|
||||
interface: powermeter
|
||||
metadata:
|
||||
license: https://opensource.org/licenses/Apache-2.0
|
||||
authors:
|
||||
- Florin Mihut
|
||||
Reference in New Issue
Block a user