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,763 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
#include "API.hpp"
#include <everest/external_energy_limits/external_energy_limits.hpp>
#include <utils/date.hpp>
#include <utils/yaml_loader.hpp>
namespace module {
static const auto NOTIFICATION_PERIOD = std::chrono::seconds(1);
static const std::string API_MODULE_SOURCE = "API_module";
SessionInfo::SessionInfo() :
start_energy_import_wh(0),
end_energy_import_wh(0),
start_energy_export_wh(0),
end_energy_export_wh(0),
latest_total_w(0),
state(State::Unknown) {
this->start_time_point = date::utc_clock::now();
this->end_time_point = this->start_time_point;
uk_random_delay_remaining.countdown_s = 0;
uk_random_delay_remaining.current_limit_after_delay_A = 0.;
uk_random_delay_remaining.current_limit_during_delay_A = 0;
}
bool SessionInfo::is_state_charging(const SessionInfo::State current_state) {
return current_state == State::AuthRequired || current_state == State::Charging ||
current_state == State::ChargingPausedEV || current_state == State::ChargingPausedEVSE;
}
void SessionInfo::reset() {
std::lock_guard<std::mutex> lock(this->session_info_mutex);
this->state = State::Unknown;
this->start_energy_import_wh = 0;
this->end_energy_import_wh = 0;
this->start_energy_export_wh = 0;
this->end_energy_export_wh = 0;
this->start_time_point = date::utc_clock::now();
this->latest_total_w = 0;
this->permanent_fault = false;
}
types::energy::ExternalLimits get_external_limits(const std::string& data, bool is_watts) {
const auto limit = std::stof(data);
const auto timestamp = Everest::Date::to_rfc3339(date::utc_clock::now());
types::energy::ExternalLimits external_limits;
types::energy::ScheduleReqEntry target_entry;
target_entry.timestamp = timestamp;
types::energy::ScheduleReqEntry zero_entry;
zero_entry.timestamp = timestamp;
if (is_watts) {
target_entry.limits_to_leaves.total_power_W = {std::fabs(limit), API_MODULE_SOURCE};
zero_entry.limits_to_leaves.total_power_W = {0, API_MODULE_SOURCE};
} else {
target_entry.limits_to_leaves.ac_max_current_A = {std::fabs(limit), API_MODULE_SOURCE};
zero_entry.limits_to_leaves.ac_max_current_A = {0, API_MODULE_SOURCE};
}
if (limit > 0) {
external_limits.schedule_import = std::vector<types::energy::ScheduleReqEntry>(1, target_entry);
external_limits.schedule_export = std::vector<types::energy::ScheduleReqEntry>(1, zero_entry);
} else {
external_limits.schedule_import = std::vector<types::energy::ScheduleReqEntry>(1, zero_entry);
external_limits.schedule_export = std::vector<types::energy::ScheduleReqEntry>(1, target_entry);
}
return external_limits;
}
types::energy::ExternalLimits get_external_limits(int32_t phases, float amps) {
const auto timestamp = Everest::Date::to_rfc3339(date::utc_clock::now());
types::energy::ExternalLimits external_limits;
types::energy::ScheduleReqEntry target_entry;
target_entry.timestamp = timestamp;
types::energy::ScheduleReqEntry zero_entry;
zero_entry.timestamp = timestamp;
zero_entry.limits_to_leaves.ac_max_current_A = {0, API_MODULE_SOURCE};
// check if phases are 1 or 3, otherwise throw an exception
const auto is_valid = (phases == 1 || phases == 3);
if (is_valid) {
target_entry.limits_to_leaves.ac_max_phase_count = {phases, API_MODULE_SOURCE};
target_entry.limits_to_leaves.ac_min_phase_count = {phases, API_MODULE_SOURCE};
target_entry.limits_to_leaves.ac_max_current_A = {std::fabs(amps), API_MODULE_SOURCE};
} else {
std::string error_msg = "Invalid phase count " + std::to_string(phases);
throw std::out_of_range(error_msg);
}
if (amps > 0) {
external_limits.schedule_import = std::vector<types::energy::ScheduleReqEntry>(1, target_entry);
} else {
external_limits.schedule_export = std::vector<types::energy::ScheduleReqEntry>(1, target_entry);
external_limits.schedule_import = std::vector<types::energy::ScheduleReqEntry>(1, zero_entry);
}
return external_limits;
}
static void remove_error_from_list(std::vector<module::SessionInfo::Error>& list, const std::string& error_type) {
list.erase(std::remove_if(list.begin(), list.end(),
[error_type](const module::SessionInfo::Error& err) { return err.type == error_type; }),
list.end());
}
void SessionInfo::update_state(const types::evse_manager::SessionEvent event) {
std::lock_guard<std::mutex> lock(this->session_info_mutex);
using Event = types::evse_manager::SessionEventEnum;
// using switch since some code analysis tools can detect missing cases
// (when new events are added)
switch (event.event) {
case Event::Enabled:
this->state = State::Unplugged;
break;
case Event::Disabled:
this->state = State::Disabled;
break;
case Event::AuthRequired:
this->state = State::AuthRequired;
break;
case Event::PrepareCharging:
case Event::SessionStarted:
case Event::TransactionStarted:
this->state = State::Preparing;
break;
case Event::ChargingStarted:
this->state = State::Charging;
break;
case Event::ChargingPausedEV:
this->state = State::ChargingPausedEV;
break;
case Event::ChargingPausedEVSE:
this->state = State::ChargingPausedEVSE;
break;
case Event::ChargingFinished:
this->state = State::Finished;
break;
case Event::StoppingCharging:
this->state = State::FinishedEV;
break;
case Event::TransactionFinished: {
if (event.transaction_finished->reason == types::evse_manager::StopTransactionReason::Local) {
this->state = State::FinishedEVSE;
} else {
this->state = State::Finished;
}
break;
}
case Event::PluginTimeout:
this->state = State::AuthTimeout;
break;
case Event::ReservationStart:
this->state = State::Reserved;
break;
case Event::ReservationEnd:
case Event::SessionFinished:
this->state = State::Unplugged;
break;
default:
break;
}
}
std::string SessionInfo::state_to_string(SessionInfo::State s) {
switch (s) {
case SessionInfo::State::Unknown:
return "Unknown";
case SessionInfo::State::Unplugged:
return "Unplugged";
case SessionInfo::State::Disabled:
return "Disabled";
case SessionInfo::State::Preparing:
return "Preparing";
case SessionInfo::State::Reserved:
return "Reserved";
case SessionInfo::State::AuthRequired:
return "AuthRequired";
case SessionInfo::State::ChargingPausedEV:
return "ChargingPausedEV";
case SessionInfo::State::ChargingPausedEVSE:
return "ChargingPausedEVSE";
case SessionInfo::State::Charging:
return "Charging";
case SessionInfo::State::Finished:
return "Finished";
case SessionInfo::State::FinishedEVSE:
return "FinishedEVSE";
case SessionInfo::State::FinishedEV:
return "FinishedEV";
case SessionInfo::State::AuthTimeout:
return "AuthTimeout";
}
return "Unknown";
}
void SessionInfo::set_start_energy_import_wh(int32_t start_energy_import_wh) {
std::lock_guard<std::mutex> lock(this->session_info_mutex);
this->start_energy_import_wh = start_energy_import_wh;
this->end_energy_import_wh = start_energy_import_wh;
this->start_time_point = date::utc_clock::now();
this->end_time_point = this->start_time_point;
}
void SessionInfo::set_end_energy_import_wh(int32_t end_energy_import_wh) {
std::lock_guard<std::mutex> lock(this->session_info_mutex);
this->end_energy_import_wh = end_energy_import_wh;
this->end_time_point = date::utc_clock::now();
}
void SessionInfo::set_latest_energy_import_wh(int32_t latest_energy_wh) {
std::lock_guard<std::mutex> lock(this->session_info_mutex);
if (this->is_state_charging(this->state)) {
this->end_time_point = date::utc_clock::now();
this->end_energy_import_wh = latest_energy_wh;
}
}
void SessionInfo::set_start_energy_export_wh(int32_t start_energy_export_wh) {
std::lock_guard<std::mutex> lock(this->session_info_mutex);
this->start_energy_export_wh = start_energy_export_wh;
this->end_energy_export_wh = start_energy_export_wh;
this->start_energy_export_wh_was_set = true;
}
void SessionInfo::set_end_energy_export_wh(int32_t end_energy_export_wh) {
std::lock_guard<std::mutex> lock(this->session_info_mutex);
this->end_energy_export_wh = end_energy_export_wh;
this->end_energy_export_wh_was_set = true;
}
void SessionInfo::set_latest_energy_export_wh(int32_t latest_export_energy_wh) {
std::lock_guard<std::mutex> lock(this->session_info_mutex);
if (this->is_state_charging(this->state)) {
this->end_energy_export_wh = latest_export_energy_wh;
this->end_energy_export_wh_was_set = true;
}
}
void SessionInfo::set_latest_total_w(double latest_total_w) {
std::lock_guard<std::mutex> lock(this->session_info_mutex);
this->latest_total_w = latest_total_w;
}
void SessionInfo::set_uk_random_delay_remaining(const types::uk_random_delay::CountDown& cd) {
std::lock_guard<std::mutex> lock(this->session_info_mutex);
this->uk_random_delay_remaining = cd;
}
void SessionInfo::set_enable_disable_source(const std::string& active_source, const std::string& active_state,
const int active_priority) {
std::lock_guard<std::mutex> lock(this->session_info_mutex);
this->active_enable_disable_source = active_source;
this->active_enable_disable_state = active_state;
this->active_enable_disable_priority = active_priority;
}
static void to_json(json& j, const SessionInfo::Error& e) {
j = json{{"type", e.type}, {"description", e.description}, {"severity", e.severity}};
}
SessionInfo::operator std::string() {
std::lock_guard<std::mutex> lock(this->session_info_mutex);
auto charged_energy_wh = this->end_energy_import_wh - this->start_energy_import_wh;
int32_t discharged_energy_wh{0};
if (this->start_energy_export_wh_was_set && this->end_energy_export_wh_was_set) {
discharged_energy_wh = this->end_energy_export_wh - this->start_energy_export_wh;
}
auto now = date::utc_clock::now();
auto charging_duration_s =
std::chrono::duration_cast<std::chrono::seconds>(this->end_time_point - this->start_time_point);
json session_info = json::object({
{"state", state_to_string(this->state)},
{"permanent_fault", this->permanent_fault},
{"charged_energy_wh", charged_energy_wh},
{"discharged_energy_wh", discharged_energy_wh},
{"latest_total_w", this->latest_total_w},
{"charging_duration_s", charging_duration_s.count()},
{"datetime", Everest::Date::to_rfc3339(now)},
});
json active_disable_enable = json::object({{"source", this->active_enable_disable_source},
{"state", this->active_enable_disable_state},
{"priority", this->active_enable_disable_priority}});
session_info["active_enable_disable_source"] = active_disable_enable;
if (uk_random_delay_remaining.countdown_s > 0) {
json random_delay =
json::object({{"remaining_s", uk_random_delay_remaining.countdown_s},
{"current_limit_after_delay_A", uk_random_delay_remaining.current_limit_after_delay_A},
{"current_limit_during_delay_A", uk_random_delay_remaining.current_limit_during_delay_A},
{"start_time", uk_random_delay_remaining.start_time.value_or("")}});
session_info["uk_random_delay"] = random_delay;
}
return session_info.dump();
}
void API::init() {
// ensure all evse_energy_sink(s) that are connected have an evse id mapping
for (const auto& evse_sink : this->r_evse_energy_sink) {
if (not evse_sink->get_mapping().has_value()) {
EVLOG_critical << "Please configure an evse mapping in your configuration file for the connected "
"r_evse_energy_sink with module_id: "
<< evse_sink->module_id;
throw std::runtime_error("At least one connected evse_energy_sink misses a mapping to an evse.");
}
}
this->limit_decimal_places = std::make_unique<LimitDecimalPlaces>(this->config);
std::vector<std::string> connectors;
std::string var_connectors = this->api_base + "connectors";
evse_manager_check.set_total(r_evse_manager.size());
for (const auto& evse : this->r_evse_manager) {
auto& session_info = this->info.emplace_back(std::make_unique<SessionInfo>());
auto& hw_caps = this->hw_capabilities_str.emplace_back("");
std::string evse_base = this->api_base + evse->module_id;
connectors.push_back(evse->module_id);
evse->subscribe_ready([this, &evse](bool ready) {
if (ready) {
this->evse_manager_check.notify_ready(evse->module_id);
}
});
// API variables
std::string var_base = evse_base + "/var/";
std::string var_hw_caps = var_base + "hardware_capabilities";
evse->subscribe_hw_capabilities(
[this, var_hw_caps, &hw_caps](types::evse_board_support::HardwareCapabilities hw_capabilities) {
hw_caps = this->limit_decimal_places->limit(hw_capabilities);
this->mqtt.publish(var_hw_caps, hw_caps);
});
std::string var_powermeter = var_base + "powermeter";
evse->subscribe_powermeter([this, var_powermeter, &session_info](types::powermeter::Powermeter powermeter) {
this->mqtt.publish(var_powermeter, this->limit_decimal_places->limit(powermeter));
session_info->set_latest_energy_import_wh(powermeter.energy_Wh_import.total);
if (powermeter.energy_Wh_export.has_value()) {
session_info->set_latest_energy_export_wh(powermeter.energy_Wh_export.value().total);
}
if (powermeter.power_W.has_value()) {
session_info->set_latest_total_w(powermeter.power_W.value().total);
}
});
std::string var_limits = var_base + "limits";
evse->subscribe_limits([this, var_limits](types::evse_manager::Limits limits) {
this->mqtt.publish(var_limits, this->limit_decimal_places->limit(limits));
});
std::string var_telemetry = var_base + "telemetry";
evse->subscribe_telemetry([this, var_telemetry](types::evse_board_support::Telemetry telemetry) {
this->mqtt.publish(var_telemetry, this->limit_decimal_places->limit(telemetry));
});
std::string var_ev_info = var_base + "ev_info";
evse->subscribe_ev_info([this, var_ev_info](types::evse_manager::EVInfo ev_info) {
json ev_info_json = ev_info;
this->mqtt.publish(var_ev_info, ev_info_json.dump());
});
std::string var_selected_protocol = var_base + "selected_protocol";
evse->subscribe_selected_protocol([this, var_selected_protocol](const std::string& selected_protocol) {
this->selected_protocol = selected_protocol;
});
evse->subscribe_error(
"evse_manager/Inoperative",
[this, &session_info](const Everest::error::Error&) { session_info->set_permanent_fault(true); },
[this, &session_info](const Everest::error::Error&) { session_info->set_permanent_fault(false); });
std::string var_datetime = var_base + "datetime";
std::string var_session_info = var_base + "session_info";
std::string var_logging_path = var_base + "logging_path";
this->api_threads.push_back(std::thread(
[this, var_datetime, var_session_info, var_hw_caps, var_selected_protocol, &session_info, &hw_caps]() {
auto next_tick = std::chrono::steady_clock::now();
while (this->running) {
std::string datetime_str = Everest::Date::to_rfc3339(date::utc_clock::now());
this->mqtt.publish(var_datetime, datetime_str);
this->mqtt.publish(var_session_info, *session_info);
this->mqtt.publish(var_hw_caps, hw_caps);
this->mqtt.publish(var_selected_protocol, this->selected_protocol);
next_tick += NOTIFICATION_PERIOD;
std::this_thread::sleep_until(next_tick);
}
}));
evse->subscribe_session_event(
[this, var_session_info, var_logging_path, &session_info](types::evse_manager::SessionEvent session_event) {
session_info->update_state(session_event);
if (session_event.source.has_value()) {
const auto source = session_event.source.value();
session_info->set_enable_disable_source(
types::evse_manager::enable_source_to_string(source.enable_source),
types::evse_manager::enable_state_to_string(source.enable_state), source.enable_priority);
}
if (session_event.event == types::evse_manager::SessionEventEnum::SessionStarted) {
if (session_event.session_started.has_value()) {
auto session_started = session_event.session_started.value();
if (session_started.logging_path.has_value()) {
this->mqtt.publish(var_logging_path, session_started.logging_path.value());
}
}
}
if (session_event.event == types::evse_manager::SessionEventEnum::TransactionStarted) {
if (session_event.transaction_started.has_value()) {
auto transaction_started = session_event.transaction_started.value();
auto energy_Wh_import = transaction_started.meter_value.energy_Wh_import.total;
session_info->set_start_energy_import_wh(energy_Wh_import);
if (transaction_started.meter_value.energy_Wh_export.has_value()) {
auto energy_Wh_export = transaction_started.meter_value.energy_Wh_export.value().total;
session_info->set_start_energy_export_wh(energy_Wh_export);
} else {
session_info->start_energy_export_wh_was_set = false;
}
}
} else if (session_event.event == types::evse_manager::SessionEventEnum::TransactionFinished) {
if (session_event.transaction_finished.has_value()) {
auto transaction_finished = session_event.transaction_finished.value();
auto energy_Wh_import = transaction_finished.meter_value.energy_Wh_import.total;
session_info->set_end_energy_import_wh(energy_Wh_import);
if (transaction_finished.meter_value.energy_Wh_export.has_value()) {
auto energy_Wh_export = transaction_finished.meter_value.energy_Wh_export.value().total;
session_info->set_end_energy_export_wh(energy_Wh_export);
} else {
session_info->end_energy_export_wh_was_set = false;
}
}
this->mqtt.publish(var_session_info, *session_info);
}
});
// API commands
std::string cmd_base = evse_base + "/cmd/";
std::string cmd_enable_disable = cmd_base + "enable_disable";
this->mqtt.subscribe(cmd_enable_disable, [this, &evse](const std::string& data) {
auto connector_id = 0;
types::evse_manager::EnableDisableSource enable_source{types::evse_manager::Enable_source::LocalAPI,
types::evse_manager::Enable_state::Enable, 100};
if (!data.empty()) {
try {
auto arg = json::parse(data);
if (arg.contains("connector_id")) {
connector_id = arg.at("connector_id");
}
if (arg.contains("source")) {
enable_source.enable_source = types::evse_manager::string_to_enable_source(arg.at("source"));
}
if (arg.contains("state")) {
enable_source.enable_state = types::evse_manager::string_to_enable_state(arg.at("state"));
}
if (arg.contains("priority")) {
enable_source.enable_priority = arg.at("priority");
}
} catch (const std::exception& e) {
EVLOG_error << "enable: Cannot parse argument, command ignored: " << e.what();
return;
}
} else {
EVLOG_error << "enable: No argument specified, ignoring command";
return;
}
this->evse_manager_check.wait_ready();
evse->call_enable_disable(connector_id, enable_source);
});
std::string cmd_disable = cmd_base + "disable";
this->mqtt.subscribe(cmd_disable, [this, &evse](const std::string& data) {
auto connector_id = 0;
types::evse_manager::EnableDisableSource enable_source{types::evse_manager::Enable_source::LocalAPI,
types::evse_manager::Enable_state::Disable, 100};
if (!data.empty()) {
try {
connector_id = std::stoi(data);
EVLOG_warning << "disable: Argument is an integer, using deprecated compatibility mode";
} catch (const std::exception& e) {
EVLOG_error << "disable: Cannot parse argument, ignoring command";
return;
}
} else {
EVLOG_error << "disable: No argument specified, ignoring command";
return;
}
this->evse_manager_check.wait_ready();
evse->call_enable_disable(connector_id, enable_source);
});
std::string cmd_enable = cmd_base + "enable";
this->mqtt.subscribe(cmd_enable, [this, &evse](const std::string& data) {
auto connector_id = 0;
types::evse_manager::EnableDisableSource enable_source{types::evse_manager::Enable_source::LocalAPI,
types::evse_manager::Enable_state::Enable, 100};
if (!data.empty()) {
try {
connector_id = std::stoi(data);
EVLOG_warning << "disable: Argument is an integer, using deprecated compatibility mode";
} catch (const std::exception& e) {
EVLOG_error << "disable: Cannot parse argument, ignoring command";
return;
}
} else {
EVLOG_error << "disable: No argument specified, ignoring command";
return;
}
this->evse_manager_check.wait_ready();
evse->call_enable_disable(connector_id, enable_source);
});
std::string cmd_pause_charging = cmd_base + "pause_charging";
this->mqtt.subscribe(cmd_pause_charging, [this, &evse](const std::string&) {
this->evse_manager_check.wait_ready();
evse->call_pause_charging(); //
});
std::string cmd_resume_charging = cmd_base + "resume_charging";
this->mqtt.subscribe(cmd_resume_charging, [this, &evse](const std::string&) {
this->evse_manager_check.wait_ready();
evse->call_resume_charging(); //
});
std::string cmd_stop_charging = cmd_base + "stop_charging";
this->mqtt.subscribe(cmd_stop_charging, [this, &evse](const std::string&) {
this->evse_manager_check.wait_ready();
types::evse_manager::StopTransactionRequest request;
request.reason = types::evse_manager::StopTransactionReason::Local;
evse->call_stop_transaction(request);
});
std::string cmd_force_unlock = cmd_base + "force_unlock";
this->mqtt.subscribe(cmd_force_unlock, [this, &evse](const std::string& data) {
int connector_id = 1;
if (!data.empty()) {
try {
connector_id = std::stoi(data);
} catch (const std::exception& e) {
EVLOG_error << "Could not parse connector id for force unlock, using " << connector_id
<< ", error: " << e.what();
}
}
// match processing in ChargePointImpl::handleUnlockConnectorRequest
// so that OCPP UnlockConnector and everest_api/evse_manager/cmd/force_unlock
// perform the same action
types::evse_manager::StopTransactionRequest req;
req.reason = types::evse_manager::StopTransactionReason::UnlockCommand;
this->evse_manager_check.wait_ready();
evse->call_stop_transaction(req);
evse->call_force_unlock(connector_id);
});
// Check if a uk_random_delay is connected that matches this evse_manager
for (const auto& random_delay : this->r_random_delay) {
if (random_delay->module_id == evse->module_id) {
random_delay->subscribe_countdown([&session_info](const types::uk_random_delay::CountDown& s) {
session_info->set_uk_random_delay_remaining(s);
});
std::string cmd_uk_random_delay = cmd_base + "uk_random_delay";
this->mqtt.subscribe(cmd_uk_random_delay, [&random_delay](const std::string& data) {
if (data == "enable") {
random_delay->call_enable();
} else if (data == "disable") {
random_delay->call_disable();
} else if (data == "cancel") {
random_delay->call_cancel();
}
});
std::string uk_random_delay_set_max_duration_s = cmd_base + "uk_random_delay_set_max_duration_s";
this->mqtt.subscribe(uk_random_delay_set_max_duration_s, [&random_delay](const std::string& data) {
int seconds = 600;
try {
seconds = std::stoi(data);
} catch (const std::exception& e) {
EVLOG_error << "Could not parse connector duration value for "
"uk_random_delay_set_max_duration_s, using default value of "
<< seconds << " seconds, error: " << e.what();
}
random_delay->call_set_duration_s(seconds);
});
}
}
}
std::string var_ocpp_connection_status = this->api_base + "ocpp/var/connection_status";
std::string var_ocpp_schedule = this->api_base + "ocpp/var/charging_schedules";
if (this->r_ocpp.size() == 1) {
this->r_ocpp.at(0)->subscribe_is_connected([this](bool is_connected) {
std::scoped_lock lock(ocpp_data_mutex);
if (is_connected) {
this->ocpp_connection_status = "connected";
} else {
this->ocpp_connection_status = "disconnected";
}
});
this->r_ocpp.at(0)->subscribe_charging_schedules([this, &var_ocpp_schedule](json schedule) {
std::scoped_lock lock(ocpp_data_mutex);
this->ocpp_charging_schedule = std::move(schedule);
this->ocpp_charging_schedule_updated = true;
});
}
std::string var_info = this->api_base + "info/var/info";
if (this->config.charger_information_file != "") {
if (not r_charger_information.empty()) {
EVLOG_warning << "The configured charger information file (" << this->config.charger_information_file
<< ") is ignored in favor of the charger information interface connection.";
} else {
auto charger_information_path = std::filesystem::path(this->config.charger_information_file);
try {
this->charger_information = Everest::load_yaml(charger_information_path);
} catch (const std::exception& err) {
EVLOG_error << "Error parsing charger information file at " << this->config.charger_information_file
<< ": " << err.what();
}
}
}
this->api_threads.emplace_back(
[this, var_connectors, connectors, var_info, var_ocpp_connection_status, var_ocpp_schedule]() {
auto next_tick = std::chrono::steady_clock::now();
while (this->running) {
json connectors_array = connectors;
this->mqtt.publish(var_connectors, connectors_array.dump());
if (not this->charger_information.is_null()) {
this->mqtt.publish(var_info, this->charger_information.dump());
}
{
std::scoped_lock lock(ocpp_data_mutex);
this->mqtt.publish(var_ocpp_connection_status, this->ocpp_connection_status);
if (this->ocpp_charging_schedule_updated) {
this->ocpp_charging_schedule_updated = false;
this->mqtt.publish(var_ocpp_schedule, ocpp_charging_schedule.dump());
}
}
next_tick += NOTIFICATION_PERIOD;
std::this_thread::sleep_until(next_tick);
}
});
}
void API::ready() {
if (not r_charger_information.empty()) {
this->charger_information = r_charger_information.at(0)->call_get_charger_information();
}
this->evse_manager_check.wait_ready();
// The following API commands require the EVSE managers to be ready
for (const auto& evse : this->r_evse_manager) {
std::string evse_base = this->api_base + evse->module_id;
std::string cmd_base = evse_base + "/cmd/";
auto evse_id = evse->call_get_evse().id;
if (external_energy_limits::is_evse_sink_configured(this->r_evse_energy_sink, evse_id)) {
auto& evse_energy_sink =
external_energy_limits::get_evse_sink_by_evse_id(this->r_evse_energy_sink, evse_id);
std::string cmd_set_limit = cmd_base + "set_limit_amps";
this->mqtt.subscribe(cmd_set_limit, [&evse_energy_sink = evse_energy_sink](const std::string& data) {
try {
const auto external_limits = get_external_limits(data, false);
evse_energy_sink.call_set_external_limits(external_limits);
} catch (const std::invalid_argument& e) {
EVLOG_warning << "Invalid limit: No conversion of given input could be performed.";
}
});
std::string cmd_set_limit_watts = cmd_base + "set_limit_watts";
this->mqtt.subscribe(cmd_set_limit_watts, [&evse_energy_sink = evse_energy_sink](const std::string& data) {
try {
const auto external_limits = get_external_limits(data, true);
evse_energy_sink.call_set_external_limits(external_limits);
} catch (const std::invalid_argument& e) {
EVLOG_warning << "Invalid limit: No conversion of given input could be performed.";
}
});
std::string cmd_set_limit_phases = cmd_base + "set_limit_amps_phases";
this->mqtt.subscribe(cmd_set_limit_phases, [&evse_energy_sink = evse_energy_sink](const std::string& data) {
int32_t phases{};
float amps{};
try {
auto arg = json::parse(data);
if (arg.contains("amps") && arg.contains("phases")) {
amps = arg.at("amps");
phases = arg.at("phases");
} else {
EVLOG_error << "Invalid limit: Missing amps or phases.";
return;
}
} catch (const std::exception& e) {
EVLOG_error << "set_limit_amps_phases: Cannot parse argument, command ignored: " << e.what();
return;
}
try {
const auto external_limits = get_external_limits(phases, amps);
evse_energy_sink.call_set_external_limits(external_limits);
} catch (const std::invalid_argument& e) {
EVLOG_warning << "Invalid limit: No conversion of given input could be performed.";
} catch (const std::out_of_range& e) {
EVLOG_warning << "Invalid limit: Out of range "
<< ", error: " << e.what();
}
});
} else {
EVLOG_warning << "No evse energy sink configured for evse_id: " << evse_id
<< ". API module does therefore not allow control of amps or power limits for this EVSE";
}
}
std::string var_active_errors = this->api_base + "errors/var/active_errors";
this->api_threads.emplace_back([this, var_active_errors]() {
auto next_tick = std::chrono::steady_clock::now();
while (this->running) {
if (not r_error_history.empty()) {
// request active errors
types::error_history::FilterArguments filter;
filter.state_filter = types::error_history::State::Active;
auto active_errors = r_error_history.at(0)->call_get_errors(filter);
json errors_json = json(active_errors);
// publish
this->mqtt.publish(var_active_errors, errors_json.dump());
}
next_tick += NOTIFICATION_PERIOD;
std::this_thread::sleep_until(next_tick);
}
});
}
} // namespace module

View File

@@ -0,0 +1,225 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#ifndef API_HPP
#define API_HPP
//
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
// template version 2
//
#include "ld-ev.hpp"
// headers for required interface implementations
#include <generated/interfaces/charger_information/Interface.hpp>
#include <generated/interfaces/error_history/Interface.hpp>
#include <generated/interfaces/evse_manager/Interface.hpp>
#include <generated/interfaces/external_energy_limits/Interface.hpp>
#include <generated/interfaces/ocpp/Interface.hpp>
#include <generated/interfaces/uk_random_delay/Interface.hpp>
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
// insert your custom include headers here
#include <condition_variable>
#include <list>
#include <memory>
#include <mutex>
#include <sstream>
#include <date/date.h>
#include <date/tz.h>
#include "StartupMonitor.hpp"
#include "limit_decimal_places.hpp"
namespace module {
class LimitDecimalPlaces;
class SessionInfo {
public:
SessionInfo();
struct Error {
std::string type;
std::string description;
std::string severity;
};
bool start_energy_export_wh_was_set{
false}; ///< Indicate if start export energy value (optional) has been received or not
bool end_energy_export_wh_was_set{
false}; ///< Indicate if end export energy value (optional) has been received or not
void reset();
void update_state(const types::evse_manager::SessionEvent event);
void set_start_energy_import_wh(int32_t start_energy_import_wh);
void set_end_energy_import_wh(int32_t end_energy_import_wh);
void set_latest_energy_import_wh(int32_t latest_energy_wh);
void set_start_energy_export_wh(int32_t start_energy_export_wh);
void set_end_energy_export_wh(int32_t end_energy_export_wh);
void set_latest_energy_export_wh(int32_t latest_export_energy_wh);
void set_latest_total_w(double latest_total_w);
void set_uk_random_delay_remaining(const types::uk_random_delay::CountDown& c);
void set_enable_disable_source(const std::string& active_source, const std::string& active_state,
const int active_priority);
void set_permanent_fault(bool f) {
permanent_fault = f;
}
/// \brief Converts this struct into a serialized json object
operator std::string();
private:
std::mutex session_info_mutex;
int32_t start_energy_import_wh; ///< Energy reading (import) at the beginning of this charging session in Wh
int32_t end_energy_import_wh; ///< Energy reading (import) at the end of this charging session in Wh
int32_t start_energy_export_wh; ///< Energy reading (export) at the beginning of this charging session in Wh
int32_t end_energy_export_wh; ///< Energy reading (export) at the end of this charging session in Wh
types::uk_random_delay::CountDown uk_random_delay_remaining; ///< Remaining time of a UK smart charging regs
///< delay. Set to 0 if no delay is active
std::chrono::time_point<date::utc_clock> start_time_point; ///< Start of the charging session
std::chrono::time_point<date::utc_clock> end_time_point; ///< End of the charging session
double latest_total_w; ///< Latest total power reading in W
enum class State {
Unknown,
Unplugged,
Disabled,
Preparing,
Reserved,
AuthRequired,
ChargingPausedEV,
ChargingPausedEVSE,
Charging,
AuthTimeout,
Finished,
FinishedEVSE,
FinishedEV
} state;
static bool is_state_charging(SessionInfo::State current_state);
std::string state_to_string(State s);
std::string active_enable_disable_source{"Unspecified"};
std::string active_enable_disable_state{"Enabled"};
int active_enable_disable_priority{0};
bool permanent_fault{false};
};
} // namespace module
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
namespace module {
struct Conf {
std::string charger_information_file;
int powermeter_energy_import_decimal_places;
int powermeter_energy_export_decimal_places;
int powermeter_power_decimal_places;
int powermeter_voltage_decimal_places;
int powermeter_VAR_decimal_places;
int powermeter_current_decimal_places;
int powermeter_frequency_decimal_places;
int hw_caps_max_current_export_decimal_places;
int hw_caps_max_current_import_decimal_places;
int hw_caps_min_current_export_decimal_places;
int hw_caps_min_current_import_decimal_places;
int hw_caps_max_plug_temperature_C_decimal_places;
int limits_max_current_decimal_places;
int telemetry_evse_temperature_C_decimal_places;
int telemetry_fan_rpm_decimal_places;
int telemetry_supply_voltage_12V_decimal_places;
int telemetry_supply_voltage_minus_12V_decimal_places;
int telemetry_plug_temperature_C_decimal_places;
double powermeter_energy_import_round_to;
double powermeter_energy_export_round_to;
double powermeter_power_round_to;
double powermeter_voltage_round_to;
double powermeter_VAR_round_to;
double powermeter_current_round_to;
double powermeter_frequency_round_to;
double hw_caps_max_current_export_round_to;
double hw_caps_max_current_import_round_to;
double hw_caps_min_current_export_round_to;
double hw_caps_min_current_import_round_to;
double hw_caps_max_plug_temperature_C_round_to;
double limits_max_current_round_to;
double telemetry_evse_temperature_C_round_to;
double telemetry_fan_rpm_round_to;
double telemetry_supply_voltage_12V_round_to;
double telemetry_supply_voltage_minus_12V_round_to;
double telemetry_plug_temperature_C_round_to;
};
class API : public Everest::ModuleBase {
public:
API() = delete;
API(const ModuleInfo& info, Everest::MqttProvider& mqtt_provider,
std::vector<std::unique_ptr<charger_informationIntf>> r_charger_information,
std::vector<std::unique_ptr<evse_managerIntf>> r_evse_manager, std::vector<std::unique_ptr<ocppIntf>> r_ocpp,
std::vector<std::unique_ptr<uk_random_delayIntf>> r_random_delay,
std::vector<std::unique_ptr<error_historyIntf>> r_error_history,
std::vector<std::unique_ptr<external_energy_limitsIntf>> r_evse_energy_sink, Conf& config) :
ModuleBase(info),
mqtt(mqtt_provider),
r_charger_information(std::move(r_charger_information)),
r_evse_manager(std::move(r_evse_manager)),
r_ocpp(std::move(r_ocpp)),
r_random_delay(std::move(r_random_delay)),
r_error_history(std::move(r_error_history)),
r_evse_energy_sink(std::move(r_evse_energy_sink)),
config(config){};
Everest::MqttProvider& mqtt;
const std::vector<std::unique_ptr<charger_informationIntf>> r_charger_information;
const std::vector<std::unique_ptr<evse_managerIntf>> r_evse_manager;
const std::vector<std::unique_ptr<ocppIntf>> r_ocpp;
const std::vector<std::unique_ptr<uk_random_delayIntf>> r_random_delay;
const std::vector<std::unique_ptr<error_historyIntf>> r_error_history;
const std::vector<std::unique_ptr<external_energy_limitsIntf>> r_evse_energy_sink;
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::vector<std::thread> api_threads;
bool running = true;
StartupMonitor evse_manager_check;
std::list<std::unique_ptr<SessionInfo>> info;
std::list<std::string> hw_capabilities_str;
std::string selected_protocol;
json charger_information;
std::unique_ptr<LimitDecimalPlaces> limit_decimal_places;
std::mutex ocpp_data_mutex;
json ocpp_charging_schedule;
bool ocpp_charging_schedule_updated = false;
std::string ocpp_connection_status = "unknown";
const std::string api_base = "everest_api/";
// 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 // API_HPP

View File

@@ -0,0 +1,32 @@
load("@rules_cc//cc:defs.bzl", "cc_test")
load("//modules:module.bzl", "cc_everest_module")
load("//third-party/bazel/toolchains:defs.bzl", "CROSS_TEST_INCOMPATIBLE")
cc_everest_module(
name = "API",
srcs = glob(
[
"*.cpp",
"*.hpp",
],
),
deps = [
"@everest-core//lib:external_energy_limits",
"@rapidyaml",
],
)
cc_test(
name = "API_test",
target_compatible_with = CROSS_TEST_INCOMPATIBLE,
srcs = [
"StartupMonitor.cpp",
"StartupMonitor.hpp",
"tests/StartupMonitor_test.cpp",
],
includes = ["."],
deps = [
"//lib/everest/log:liblog",
"@googletest//:gtest_main",
],
)

View File

@@ -0,0 +1,30 @@
#
# 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
ryml::ryml
everest::external_energy_limits
everest::yaml
)
target_sources(${MODULE_NAME}
PRIVATE
"limit_decimal_places.cpp"
"StartupMonitor.cpp"
)
# ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1
# insert other things like install cmds etc here
if(EVEREST_CORE_BUILD_TESTING)
add_subdirectory(tests)
endif()
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1

View File

@@ -0,0 +1,287 @@
# API module documentation
This module is responsible for providing a simple MQTT based API to EVerest internals
## Periodically published variables for each connected EvseManager
This module periodically publishes the following variables for each connected EvseManager.
### everest_api/connectors
This variable is published every second and contains an array of the connectors for which the api is available:
```
["evse_manager"]
```
The following documentation assumes that the only connector available is called "evse_manager".
### everest_api/evse_manager/var/datetime
This variable is published every second and contains a string representation of the current UTC datetime in RFC3339 format:
```
2022-10-11T16:18:57.746Z
```
### everest_api/evse_manager/var/hardware_capabilities
This variable is published every second and contains the hardware capabilities in the following format:
```json
{
"max_current_A_export":16.0,
"max_current_A_import":32.0,
"max_phase_count_export":3,
"max_phase_count_import":3,
"min_current_A_export":0.0,
"min_current_A_import":6.0,
"min_phase_count_export":1,
"min_phase_count_import":1,
"supports_changing_phases_during_charging":true
}
```
### everest_api/evse_manager/var/session_info
This variable is published every second and contains a json object with information relating to the current charging session in the following format:
```json
{
"charged_energy_wh": 0,
"charging_duration_s": 84,
"datetime": "2022-10-11T16:48:35.747Z",
"discharged_energy_wh": 0,
"latest_total_w": 0.0,
"permanent_fault": false,
"state": "Unplugged",
"active_enable_disable_source": {
"source": "Unspecified",
"state": "Enable",
"priority": 5000
},
"uk_random_delay": {
"remaining_s": 34,
"current_limit_after_delay_A": 16.0,
"current_limit_during_delay_A": 0.0,
"start_time": "2024-02-28T14:11:11.129Z"
},
"last_enable_disable_source": "Unspecified"
}
```
- **charged_energy_wh** contains the charged energy in Wh
- **charging_duration_s** contains the duration of the current charging session in seconds
- **datetime** contains a string representation of the current UTC datetime in RFC3339 format
- **discharged_energy_wh** contains the energy fed into the power grid by the EV in Wh
- **latest_total_w** contains the latest total power reading over all phases in Watt
- **uk_random_delay_remaining_s** Remaining time of a currently active random delay according to UK smart charging regulations. Not set if no delay is active.
- **state** contains the current state of the charging session, from a list of the following possible states:
- Unplugged
- Disabled
- Preparing
- Reserved
- AuthRequired
- Charging
- ChargingPausedEV
- ChargingPausedEVSE
- Finished
- FinishedEV
- FinishedEVSE
- AuthTimeout
### everest_api/evse_manager/var/limits
This variable is published every second and contains a json object with information
relating to the current limits of this EVSE.
```json
{
"max_current": 16.0,
"nr_of_phases_available": 1,
"uuid": "evse_manager"
}
```
### everest_api/evse_manager/var/telemetry
This variable is published every second and contains telemetry of the EVSE.
```json
{
"fan_rpm": 0.0,
"rcd_current": 0.0991784930229187,
"relais_on": false,
"supply_voltage_12V": 11.950915336608887,
"supply_voltage_minus_12V": -11.94166374206543,
"temperature": 30.729248046875
}
```
### everest_api/evse_manager/var/powermeter
This variable is published every second and contains powermeter information
of the EVSE.
```json
{
"current_A": {
"L1": 16.113445281982422,
"L2": 16.113445281982422,
"L3": 16.113445281982422,
"N": 0.20141807198524475
},
"energy_Wh_import": {
"L1": 1537.3179931640625,
"L2": 1537.3179931640625,
"L3": 1537.3179931640625,
"total": 4611.9541015625
},
"frequency_Hz": {
"L1": 50.03734588623047,
"L2": 50.03734588623047,
"L3": 50.03734588623047
},
"meter_id": "YETI_POWERMETER",
"phase_seq_error": false,
"power_W": {
"L1": 3602.54833984375,
"L2": 3602.54833984375,
"L3": 3602.54833984375,
"total": 10807.64453125
},
"timestamp": 1665509120.0,
"voltage_V": {
"L1": 223.5740509033203,
"L2": 223.5740509033203,
"L3": 223.5740509033203
}
}
```
## Periodically published variables for OCPP
### everest_api/ocpp/var/connection_status
This variable is published every second and contains the connection
status of the OCPP module.
If the OCPP module has not yet published its "is_connected" status or
no OCPP module is configured "unknown" is published. Otherwise "connected"
or "disconnected" are published.
## Commands and variables published in response
### everest_api/evse_manager/cmd/enable_disable
Command to enable or disable a connector on the EVSE. The payload should be
the following json:
```json
{
"connector_id": 0,
"source": "LocalAPI",
"state": "Enable",
"priority": 42
}
```
connector_id is a positive integer identifying the connector that should be
enabled. If the connector_id is 0 the whole EVSE is enabled.
The source is an enum of the following source types :
- Unspecified
- LocalAPI
- LocalKeyLock
- ServiceTechnician
- RemoteKeyLock
- MobileApp
- FirmwareUpdate
- CSMS
The state can be either "enable", "disable", or "unassigned".
"enable" and "disable" enforce the state to be enable/disable, while unassigned means
that the source does not care about the state and other sources may decide.
Each call to this command will update an internal table that looks like this:
| Source | State | Priority |
| ------------ | ---------- | -------- |
| Unspecified | unassigned | 10000 |
| LocalAPI | disable | 42 |
| LocalKeyLock | enable | 0 |
Evaluation will be done based on priorities. 0 is the highest priority,
10000 the lowest, so in this example the connector will be enabled regardless
of what other sources say.
Imagine LocalKeyLock sends a "unassigned, prio 0", the table will then look like this:
| Source | State | Priority |
| ------------ | ---------- | -------- |
| Unspecified | unassigned | 10000 |
| LocalAPI | disable | 42 |
| LocalKeyLock | unassigned | 0 |
So now the connector will be disabled, because the second highest priority (42) sets it to disabled.
If all sources are unassigned, the connector is enabled.
If two sources have the same priority, "disabled" has priority over "enabled".
### everest_api/evse_manager/cmd/enable
Legacy command to enable a connector on the EVSE kept for compatibility reasons.
They payload should be a positive integer identifying the connector that should be enabled.
If the payload is 0 the whole EVSE is enabled.
It will actually call the following command on everest_api/evse_manager/cmd/enable_enable:
```json
{
"connector_id": 1,
"source": "LocalAPI",
"state": "Enable",
"priority": 100
}
```
### everest_api/evse_manager/cmd/disable
Legacy command to enable a connector on the EVSE kept for compatibility reasons.
Command to disable a connector on the EVSE. They payload should be a positive integer
identifying the connector that should be disabled. If the payload is 0 the whole EVSE is disabled.
It will actually call the following command on everest_api/evse_manager/cmd/enable_disable:
```json
{
"connector_id": 1,
"source": "LocalAPI",
"state": "Disable",
"priority": 100
}
```
### everest_api/evse_manager/cmd/pause_charging
If any arbitrary payload is published to this topic charging will be paused by the EVSE.
### everest_api/evse_manager/cmd/resume_charging
If any arbitrary payload is published to this topic charging will be resumed by the EVSE.
### everest_api/evse_manager/cmd/stop_charging
If any arbitrary payload is published to this topic charging will be stopped by the EVSE.
### everest_api/evse_manager/cmd/set_limit_amps
Command to set an amps limit for this EVSE that will be considered within the EnergyManager. This does not automatically imply that this limit will be set by the EVSE because the energymanagement might consider limitations from other sources, too. The payload can be a positive or negative number.
📌 **Note:** You have to configure one evse_energy_sink connection per EVSE within the configuration file in order to use this topic!
### everest_api/evse_manager/cmd/set_limit_watts
Command to set a watt limit for this EVSE that will be considered within the EnergyManager. This does not automatically imply that this limit will be set by the EVSE because the energymanagement might consider limitations from other sources, too. The payload can be a positive or negative number.
📌 **Note:** You have to configure one evse_energy_sink connection per EVSE within the configuration file in order to use this topic!
### everest_api/evse_manager/cmd/set_limit_amps_phases
Command to set a current (amps) and a phase limit for this EVSE, which will be considered by the energy
management. The payload should be in the following json format:
```json
{
"amps": 8.0,
"phases": 3
}
```
Setting these limits does not automatically imply that they will be set by the EVSE because the
energy management might consider limitations from other sources, too. The "amps" value can be a
positive or negative number. The "phases" value must be either 1 or 3.
Please consider that switching between AC single-phase (1ph) and three-phase (3ph) charging does only
work if 1ph/3ph switching is activated in the EVerest configuration. For more information please look
in the EVerest documentation.
📌 **Note:** You have to configure one evse_energy_sink connection per EVSE within the configuration file in order to use this topic!
### everest_api/evse_manager/cmd/force_unlock
Command to force unlock a connector on the EVSE. The payload should be a positive integer identifying the connector that should be unlocked. If the payload is empty or cannot be converted to an integer connector 1 is assumed.
### everest_api/evse_manager/cmd/uk_random_delay
Command to control the UK Smart Charging random delay feature. The payload can be the following enum: "enable" and "disable" to enable/disable the feature entirely or "cancel" to cancel an ongoing delay.
### everest_api/evse_manager/cmd/uk_random_delay_set_max_duration_s
Command to set the UK Smart Charging random delay maximum duration. Payload is an integer in seconds.
### everest_api/errors/var/active_errors
Publishes an array of all active errors of the charging station

View File

@@ -0,0 +1,76 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#include "StartupMonitor.hpp"
#include <everest/logging.hpp>
#include <memory>
namespace module {
bool StartupMonitor::check_ready() {
bool result{false};
if (ready_set) {
result = ready_set->size() >= n_managers;
}
return result;
}
bool StartupMonitor::set_total(std::uint8_t total) {
bool result{true};
{
std::lock_guard lock(mutex);
if (!ready_set) {
n_managers = total;
if (total == 0) {
managers_ready = true;
} else {
managers_ready = false;
ready_set = std::make_unique<ready_t>();
}
} else {
// already set
EVLOG_error << "Invalid attempt to set number of EVSE managers";
result = false;
}
}
if (total == 0) {
cv.notify_all();
}
return result;
}
void StartupMonitor::wait_ready() {
std::unique_lock lock(mutex);
cv.wait(lock, [this] { return this->managers_ready; });
}
bool StartupMonitor::notify_ready(const std::string& evse_manager_id) {
bool result{true};
bool notify{false};
{
std::lock_guard lock(mutex);
if (ready_set) {
ready_set->insert(evse_manager_id);
notify = StartupMonitor::check_ready();
if (notify) {
managers_ready = true;
n_managers = 0;
ready_set->clear(); // reclaim memory
}
} else {
result = false;
if (managers_ready) {
EVLOG_warning << "EVSE manager ready after complete";
} else {
EVLOG_error << "EVSE manager ready before total number set";
}
}
}
if (notify) {
cv.notify_all();
}
return result;
}
} // namespace module

View File

@@ -0,0 +1,68 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#ifndef STARTUPMONITOR_HPP
#define STARTUPMONITOR_HPP
#include <condition_variable>
#include <cstdint>
#include <memory>
#include <mutex>
#include <set>
#include <string>
namespace module {
/**
* \brief collect ready responses from all EVSE managers
*
* Provides a mechanism for API code to wait for all EVSE managers to be ready.
* Every EVSE manager is expected to set a `ready` variable to true. This class
* collects the IDs of EVSE managers to check that the expected number are
* ready before allowing API calls to proceed.
*
* \note an EVSE manager is not expected to set `ready` more than once, however
* this class manages this so that the `ready` is only counted once.
*/
class StartupMonitor {
private:
using ready_t = std::set<std::string>;
std::condition_variable cv;
std::mutex mutex;
protected:
std::unique_ptr<ready_t> ready_set; //!< set of received ready responses
std::uint16_t n_managers{0}; //!< total number of EVSE managers
bool managers_ready{false}; //!< all EVSE managers are ready
/**
* \brief check whether all ready responses have been received
* \returns true when the ready set contains at least n_managers responses
*/
bool check_ready();
public:
/**
* \brief set the total number of EVSE managers
* \param[in] total the number of EVSE managers
* \returns false if the total has already been set
*/
bool set_total(std::uint8_t total);
/**
* \brief wait for all EVSE managers to be ready
*/
void wait_ready();
/**
* \brief notify that a specific EVSE manager is ready
* \param[in] evse_manager_id the ID of the EVSE manager
* \returns false if the total has not been set
* \note notify_ready() may be called multiple times with the same evse_manager_id
*/
bool notify_ready(const std::string& evse_manager_id);
};
} // namespace module
#endif // STARTUPMONITOR_HPP

View File

@@ -0,0 +1,5 @@
model_name: "BelayBox"
pcb_serial_number: "0123"
charger_serial_number: "0123"
firmware_version: "v0.1.2"
hardware_version: "v0.1.2"

View File

@@ -0,0 +1,313 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#include "limit_decimal_places.hpp"
#include <cmath>
#include <c4/format.hpp>
#include <ryml.hpp>
#include <ryml_std.hpp>
namespace module {
std::string LimitDecimalPlaces::limit(const types::powermeter::Powermeter& powermeter) {
ryml::Tree tree;
ryml::NodeRef root = tree.rootref();
root |= ryml::MAP;
// add informative power meter entries
root["timestamp"] << powermeter.timestamp;
if (powermeter.meter_id.has_value()) {
root["meter_id"] << powermeter.meter_id.value();
}
if (powermeter.phase_seq_error.has_value()) {
root["phase_seq_error"] << ryml::fmt::boolalpha(powermeter.phase_seq_error.value());
}
// limit decimal places
// energy_Wh_import always exists
root["energy_Wh_import"] |= ryml::MAP;
root["energy_Wh_import"]["total"] << ryml::fmt::real(
this->round_to_nearest_step(powermeter.energy_Wh_import.total, this->config.powermeter_energy_import_round_to),
this->config.powermeter_energy_import_decimal_places);
if (powermeter.energy_Wh_import.L1.has_value()) {
root["energy_Wh_import"]["L1"] << ryml::fmt::real(
this->round_to_nearest_step(powermeter.energy_Wh_import.L1.value(),
this->config.powermeter_energy_import_round_to),
this->config.powermeter_energy_import_decimal_places);
}
if (powermeter.energy_Wh_import.L2.has_value()) {
root["energy_Wh_import"]["L2"] << ryml::fmt::real(
this->round_to_nearest_step(powermeter.energy_Wh_import.L2.value(),
this->config.powermeter_energy_import_round_to),
this->config.powermeter_energy_import_decimal_places);
}
if (powermeter.energy_Wh_import.L3.has_value()) {
root["energy_Wh_import"]["L3"] << ryml::fmt::real(
this->round_to_nearest_step(powermeter.energy_Wh_import.L3.value(),
this->config.powermeter_energy_import_round_to),
this->config.powermeter_energy_import_decimal_places);
}
// everything else in the power meter is optional
if (powermeter.energy_Wh_export.has_value()) {
auto& energy_Wh_export = powermeter.energy_Wh_export.value();
root["energy_Wh_export"] |= ryml::MAP;
root["energy_Wh_export"]["total"] << ryml::fmt::real(
this->round_to_nearest_step(energy_Wh_export.total, this->config.powermeter_energy_export_round_to),
this->config.powermeter_energy_export_decimal_places);
if (energy_Wh_export.L1.has_value()) {
root["energy_Wh_export"]["L1"]
<< ryml::fmt::real(this->round_to_nearest_step(energy_Wh_export.L1.value(),
this->config.powermeter_energy_export_round_to),
this->config.powermeter_energy_export_decimal_places);
}
if (energy_Wh_export.L2.has_value()) {
root["energy_Wh_export"]["L2"]
<< ryml::fmt::real(this->round_to_nearest_step(energy_Wh_export.L2.value(),
this->config.powermeter_energy_export_round_to),
this->config.powermeter_energy_export_decimal_places);
}
if (energy_Wh_export.L3.has_value()) {
root["energy_Wh_export"]["L3"]
<< ryml::fmt::real(this->round_to_nearest_step(energy_Wh_export.L3.value(),
this->config.powermeter_energy_export_round_to),
this->config.powermeter_energy_export_decimal_places);
}
}
if (powermeter.power_W.has_value()) {
auto& power_W = powermeter.power_W.value();
root["power_W"] |= ryml::MAP;
root["power_W"]["total"] << ryml::fmt::real(
this->round_to_nearest_step(power_W.total, this->config.powermeter_power_round_to),
this->config.powermeter_power_decimal_places);
if (power_W.L1.has_value()) {
root["power_W"]["L1"] << ryml::fmt::real(
this->round_to_nearest_step(power_W.L1.value(), this->config.powermeter_power_round_to),
this->config.powermeter_power_decimal_places);
}
if (power_W.L2.has_value()) {
root["power_W"]["L2"] << ryml::fmt::real(
this->round_to_nearest_step(power_W.L2.value(), this->config.powermeter_power_round_to),
this->config.powermeter_power_decimal_places);
}
if (power_W.L3.has_value()) {
root["power_W"]["L3"] << ryml::fmt::real(
this->round_to_nearest_step(power_W.L3.value(), this->config.powermeter_power_round_to),
this->config.powermeter_power_decimal_places);
}
}
if (powermeter.voltage_V.has_value()) {
auto& voltage_V = powermeter.voltage_V.value();
root["voltage_V"] |= ryml::MAP;
if (voltage_V.DC.has_value()) {
root["voltage_V"]["DC"] << ryml::fmt::real(
this->round_to_nearest_step(voltage_V.DC.value(), this->config.powermeter_voltage_round_to),
this->config.powermeter_voltage_decimal_places);
}
if (voltage_V.L1.has_value()) {
root["voltage_V"]["L1"] << ryml::fmt::real(
this->round_to_nearest_step(voltage_V.L1.value(), this->config.powermeter_voltage_round_to),
this->config.powermeter_voltage_decimal_places);
}
if (voltage_V.L2.has_value()) {
root["voltage_V"]["L2"] << ryml::fmt::real(
this->round_to_nearest_step(voltage_V.L2.value(), this->config.powermeter_voltage_round_to),
this->config.powermeter_voltage_decimal_places);
}
if (voltage_V.L3.has_value()) {
root["voltage_V"]["L3"] << ryml::fmt::real(
this->round_to_nearest_step(voltage_V.L3.value(), this->config.powermeter_voltage_round_to),
this->config.powermeter_voltage_decimal_places);
}
}
if (powermeter.VAR.has_value()) {
auto& VAR = powermeter.VAR.value();
root["VAR"] |= ryml::MAP;
root["VAR"]["total"] << ryml::fmt::real(
this->round_to_nearest_step(VAR.total, this->config.powermeter_VAR_round_to),
this->config.powermeter_VAR_decimal_places);
if (VAR.L1.has_value()) {
root["VAR"]["L1"] << ryml::fmt::real(
this->round_to_nearest_step(VAR.L1.value(), this->config.powermeter_VAR_round_to),
this->config.powermeter_VAR_decimal_places);
}
if (VAR.L2.has_value()) {
root["VAR"]["L2"] << ryml::fmt::real(
this->round_to_nearest_step(VAR.L2.value(), this->config.powermeter_VAR_round_to),
this->config.powermeter_VAR_decimal_places);
}
if (VAR.L3.has_value()) {
root["VAR"]["L3"] << ryml::fmt::real(
this->round_to_nearest_step(VAR.L3.value(), this->config.powermeter_VAR_round_to),
this->config.powermeter_VAR_decimal_places);
}
}
if (powermeter.current_A.has_value()) {
auto& current_A = powermeter.current_A.value();
root["current_A"] |= ryml::MAP;
if (current_A.DC.has_value()) {
root["current_A"]["DC"] << ryml::fmt::real(
this->round_to_nearest_step(current_A.DC.value(), this->config.powermeter_current_round_to),
this->config.powermeter_current_decimal_places);
}
if (current_A.L1.has_value()) {
root["current_A"]["L1"] << ryml::fmt::real(
this->round_to_nearest_step(current_A.L1.value(), this->config.powermeter_current_round_to),
this->config.powermeter_current_decimal_places);
}
if (current_A.L2.has_value()) {
root["current_A"]["L2"] << ryml::fmt::real(
this->round_to_nearest_step(current_A.L2.value(), this->config.powermeter_current_round_to),
this->config.powermeter_current_decimal_places);
}
if (current_A.L3.has_value()) {
root["current_A"]["L3"] << ryml::fmt::real(
this->round_to_nearest_step(current_A.L3.value(), this->config.powermeter_current_round_to),
this->config.powermeter_current_decimal_places);
}
if (current_A.N.has_value()) {
root["current_A"]["N"] << ryml::fmt::real(
this->round_to_nearest_step(current_A.N.value(), this->config.powermeter_current_round_to),
this->config.powermeter_current_decimal_places);
}
}
if (powermeter.frequency_Hz.has_value()) {
auto& frequency_Hz = powermeter.frequency_Hz.value();
root["frequency_Hz"] |= ryml::MAP;
root["frequency_Hz"]["L1"] << ryml::fmt::real(
this->round_to_nearest_step(frequency_Hz.L1, this->config.powermeter_frequency_round_to),
this->config.powermeter_frequency_decimal_places);
if (frequency_Hz.L2.has_value()) {
root["frequency_Hz"]["L2"] << ryml::fmt::real(
this->round_to_nearest_step(frequency_Hz.L2.value(), this->config.powermeter_frequency_round_to),
this->config.powermeter_frequency_decimal_places);
}
if (frequency_Hz.L3.has_value()) {
root["frequency_Hz"]["L3"] << ryml::fmt::real(
this->round_to_nearest_step(frequency_Hz.L3.value(), this->config.powermeter_frequency_round_to),
this->config.powermeter_frequency_decimal_places);
}
}
std::stringstream power_meter_stream;
power_meter_stream << ryml::as_json(tree);
return power_meter_stream.str();
}
std::string LimitDecimalPlaces::limit(const types::evse_board_support::HardwareCapabilities& hw_capabilities) {
ryml::Tree tree;
ryml::NodeRef root = tree.rootref();
root |= ryml::MAP;
// add informative hardware capabilities entries
root["max_phase_count_import"] << hw_capabilities.max_phase_count_import;
root["min_phase_count_import"] << hw_capabilities.min_phase_count_import;
root["max_phase_count_export"] << hw_capabilities.max_phase_count_export;
root["min_phase_count_export"] << hw_capabilities.min_phase_count_export;
root["supports_changing_phases_during_charging"]
<< ryml::fmt::boolalpha(hw_capabilities.supports_changing_phases_during_charging);
// limit decimal places
root["max_current_A_import"] << ryml::fmt::real(
this->round_to_nearest_step(hw_capabilities.max_current_A_import,
this->config.hw_caps_max_current_import_round_to),
this->config.hw_caps_max_current_import_decimal_places);
root["min_current_A_import"] << ryml::fmt::real(
this->round_to_nearest_step(hw_capabilities.min_current_A_import,
this->config.hw_caps_min_current_import_round_to),
this->config.hw_caps_max_current_import_decimal_places);
root["max_current_A_export"] << ryml::fmt::real(
this->round_to_nearest_step(hw_capabilities.max_current_A_export,
this->config.hw_caps_max_current_export_round_to),
this->config.hw_caps_max_current_import_decimal_places);
root["min_current_A_export"] << ryml::fmt::real(
this->round_to_nearest_step(hw_capabilities.min_current_A_export,
this->config.hw_caps_min_current_export_round_to),
this->config.hw_caps_min_current_export_decimal_places);
if (hw_capabilities.max_plug_temperature_C.has_value()) {
root["max_plug_temperature_C"] << ryml::fmt::real(
this->round_to_nearest_step(hw_capabilities.max_plug_temperature_C.value(),
this->config.hw_caps_max_plug_temperature_C_round_to),
this->config.hw_caps_max_plug_temperature_C_decimal_places);
}
root["connector_type"] << types::evse_board_support::connector_type_to_string(hw_capabilities.connector_type);
std::stringstream hardware_capabilities_stream;
hardware_capabilities_stream << ryml::as_json(tree);
return hardware_capabilities_stream.str();
}
std::string LimitDecimalPlaces::limit(const types::evse_manager::Limits& limits) {
ryml::Tree tree;
ryml::NodeRef root = tree.rootref();
root |= ryml::MAP;
// add informative limits entries
if (limits.uuid.has_value()) {
root["uuid"] << limits.uuid.value();
}
root["nr_of_phases_available"] << limits.nr_of_phases_available;
// limit decimal places
root["max_current"] << ryml::fmt::real(
this->round_to_nearest_step(limits.max_current, this->config.limits_max_current_round_to),
this->config.limits_max_current_decimal_places);
std::stringstream limits_stream;
limits_stream << ryml::as_json(tree);
return limits_stream.str();
}
std::string LimitDecimalPlaces::limit(const types::evse_board_support::Telemetry& telemetry) {
ryml::Tree tree;
ryml::NodeRef root = tree.rootref();
root |= ryml::MAP;
root["phase_seq_error"] << ryml::fmt::boolalpha(telemetry.relais_on);
// limit decimal places
root["temperature"] << ryml::fmt::real(
this->round_to_nearest_step(telemetry.evse_temperature_C, this->config.telemetry_evse_temperature_C_round_to),
this->config.telemetry_evse_temperature_C_decimal_places);
root["fan_rpm"] << ryml::fmt::real(
this->round_to_nearest_step(telemetry.fan_rpm, this->config.telemetry_fan_rpm_round_to),
this->config.telemetry_fan_rpm_decimal_places);
root["supply_voltage_12V"] << ryml::fmt::real(
this->round_to_nearest_step(telemetry.supply_voltage_12V, this->config.telemetry_supply_voltage_12V_round_to),
this->config.telemetry_supply_voltage_12V_decimal_places);
root["supply_voltage_minus_12V"] << ryml::fmt::real(
this->round_to_nearest_step(telemetry.supply_voltage_minus_12V,
this->config.telemetry_supply_voltage_minus_12V_round_to),
this->config.telemetry_supply_voltage_minus_12V_decimal_places);
if (telemetry.plug_temperature_C.has_value()) {
root["plug_temperature_C"] << ryml::fmt::real(
this->round_to_nearest_step(telemetry.plug_temperature_C.value(),
this->config.telemetry_plug_temperature_C_round_to),
this->config.telemetry_plug_temperature_C_decimal_places);
}
std::stringstream telemetry_stream;
telemetry_stream << ryml::as_json(tree);
return telemetry_stream.str();
}
double LimitDecimalPlaces::round_to_nearest_step(double value, double step) {
if (step <= 0) {
return value;
}
return std::round(value / step) * step;
}
} // namespace module

View File

@@ -0,0 +1,29 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#ifndef LIMIT_DECIMAL_PLACES_HPP
#define LIMIT_DECIMAL_PLACES_HPP
#include <generated/interfaces/evse_manager/Interface.hpp>
#include "API.hpp"
namespace module {
struct Conf;
class LimitDecimalPlaces {
public:
LimitDecimalPlaces(const Conf& config) : config(config){};
std::string limit(const types::powermeter::Powermeter& powermeter);
std::string limit(const types::evse_board_support::HardwareCapabilities& hw_capabilities);
std::string limit(const types::evse_manager::Limits& limits);
std::string limit(const types::evse_board_support::Telemetry& telemetry);
double round_to_nearest_step(double value, double step);
private:
const Conf& config;
};
} // namespace module
#endif // LIMIT_DECIMAL_PLACES_HPP

View File

@@ -0,0 +1,203 @@
description: >-
The EVerest API module, exposing some internal functionality on an external
MQTT connection.
config:
charger_information_file:
description: >-
Path to a file containing information about the charger like its serial number.
The content of this file is not used, when the optional dependency `charger_information`
is used.
type: string
default: ""
powermeter_energy_import_decimal_places:
description: Maximum number of decimal places for import energy in the power meter
type: integer
default: 2
minimum: 0
powermeter_energy_export_decimal_places:
description: Maximum number of decimal places for export energy in the power meter
type: integer
default: 2
minimum: 0
powermeter_power_decimal_places:
description: Maximum number of decimal places for power in the power meter
type: integer
default: 2
minimum: 0
powermeter_voltage_decimal_places:
description: Maximum number of decimal places for voltage in the power meter
type: integer
default: 2
minimum: 0
powermeter_VAR_decimal_places:
description: Maximum number of decimal places for VAR in the power meter
type: integer
default: 2
minimum: 0
powermeter_current_decimal_places:
description: Maximum number of decimal places for current in the power meter
type: integer
default: 2
minimum: 0
powermeter_frequency_decimal_places:
description: Maximum number of decimal places for frequency in the power meter
type: integer
default: 2
minimum: 0
hw_caps_max_current_export_decimal_places:
description: Maximum number of decimal places for maximum export current in the hardware capabilities
type: integer
default: 2
minimum: 0
hw_caps_max_current_import_decimal_places:
description: Maximum number of decimal places for maximum import current in the hardware capabilities
type: integer
default: 2
minimum: 0
hw_caps_min_current_export_decimal_places:
description: Maximum number of decimal places for minimum export current in the hardware capabilities
type: integer
default: 2
minimum: 0
hw_caps_min_current_import_decimal_places:
description: Maximum number of decimal places for minimum import current in the hardware capabilities
type: integer
default: 2
minimum: 0
hw_caps_max_plug_temperature_C_decimal_places:
description: Maximum number of decimal places for max_plug_temperature_C in the hardware capabilities
type: integer
default: 2
minimum: 0
limits_max_current_decimal_places:
description: Maximum number of decimal places for maximum current in the limits
type: integer
default: 2
minimum: 0
telemetry_evse_temperature_C_decimal_places:
description: Maximum number of decimal places for evse_temperature_C in telemetry
type: integer
default: 2
minimum: 0
telemetry_fan_rpm_decimal_places:
description: Maximum number of decimal places for fan RPM in telemetry
type: integer
default: 2
minimum: 0
telemetry_supply_voltage_12V_decimal_places:
description: Maximum number of decimal places for supply voltage 12V in telemetry
type: integer
default: 2
minimum: 0
telemetry_supply_voltage_minus_12V_decimal_places:
description: Maximum number of decimal places for supply voltage -12V in telemetry
type: integer
default: 2
minimum: 0
telemetry_plug_temperature_C_decimal_places:
description: Maximum number of decimal places for RCD current in telemetry
type: integer
default: 2
minimum: 0
powermeter_energy_import_round_to:
description: Round import energy to the nearest step. Ignored if value is 0
type: number
default: 0
powermeter_energy_export_round_to:
description: Round export energy to the nearest step. Ignored if value is 0
type: number
default: 0
powermeter_power_round_to:
description: Round power to the nearest step. Ignored if value is 0
type: number
default: 0
powermeter_voltage_round_to:
description: Round voltage to the nearest step. Ignored if value is 0
type: number
default: 0
powermeter_VAR_round_to:
description: Round VAR to the nearest step. Ignored if value is 0
type: number
default: 0
powermeter_current_round_to:
description: Round current to the nearest step. Ignored if value is 0
type: number
default: 0
powermeter_frequency_round_to:
description: Round frequency to the nearest step. Ignored if value is 0
type: number
default: 0
hw_caps_max_current_export_round_to:
description: Round maximum export current in hardware limits to the nearest step. Ignored if value is 0
type: number
default: 0
hw_caps_max_current_import_round_to:
description: Round maximum import current in hardware limits to the nearest step. Ignored if value is 0
type: number
default: 0
hw_caps_min_current_export_round_to:
description: Round minimum export current in hardware limits to the nearest step. Ignored if value is 0
type: number
default: 0
hw_caps_min_current_import_round_to:
description: Round minimum import current in hardware limits to the nearest step. Ignored if value is 0
type: number
default: 0
hw_caps_max_plug_temperature_C_round_to:
description: Round max_plug_temperature_C in hardware limits to the nearest step. Ignored if value is 0
type: number
default: 0
limits_max_current_round_to:
description: Round maximum current in limits to the nearest step. Ignored if value is 0
type: number
default: 0
telemetry_evse_temperature_C_round_to:
description: Round evse_temperature_C in telemetry to the nearest step. Ignored if value is 0
type: number
default: 0
telemetry_fan_rpm_round_to:
description: Round fan RPM in telemetry to the nearest step. Ignored if value is 0
type: number
default: 0
telemetry_supply_voltage_12V_round_to:
description: Round supply voltage 12V in telemetry to the nearest step. Ignored if value is 0
type: number
default: 0
telemetry_supply_voltage_minus_12V_round_to:
description: Round supply voltage -12V in telemetry to the nearest step. Ignored if value is 0
type: number
default: 0
telemetry_plug_temperature_C_round_to:
description: Round plug_temperature_C in telemetry to the nearest step. Ignored if value is 0
type: number
default: 0
requires:
charger_information:
interface: charger_information
min_connections: 0
max_connections: 1
evse_manager:
interface: evse_manager
min_connections: 1
max_connections: 128
ocpp:
interface: ocpp
min_connections: 0
max_connections: 1
random_delay:
interface: uk_random_delay
min_connections: 0
max_connections: 128
error_history:
interface: error_history
min_connections: 0
max_connections: 1
evse_energy_sink:
interface: external_energy_limits
min_connections: 0
max_connections: 128
enable_external_mqtt: true
metadata:
license: https://opensource.org/licenses/Apache-2.0
authors:
- Kai-Uwe Hermann

View File

@@ -0,0 +1,23 @@
set(TEST_TARGET_NAME ${PROJECT_NAME}_API_tests)
set(TESTS_INCLUDE_DIR "${PROJECT_SOURCE_DIR}/tests/include")
add_executable(${TEST_TARGET_NAME})
add_dependencies(${TEST_TARGET_NAME} ${MODULE_NAME})
target_include_directories(${TEST_TARGET_NAME} PRIVATE
. .. ${TESTS_INCLUDE_DIR}
)
target_sources(${TEST_TARGET_NAME} PRIVATE
StartupMonitor_test.cpp
../StartupMonitor.cpp
)
target_link_libraries(${TEST_TARGET_NAME} PRIVATE
GTest::gtest_main
)
add_test(${TEST_TARGET_NAME} ${TEST_TARGET_NAME})
ev_register_test_target(${TEST_TARGET_NAME})

View File

@@ -0,0 +1,124 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#include <gtest/gtest.h>
#include "StartupMonitor.hpp"
#include <thread>
namespace {
using namespace module;
struct StartupMonitorTest : public StartupMonitor {
[[nodiscard]] constexpr bool startup_complete() const {
return managers_ready;
}
[[nodiscard]] constexpr std::uint8_t total() const {
return n_managers;
}
[[nodiscard]] inline std::uint8_t startup_count() const {
return (ready_set) ? ready_set->size() : 0;
}
};
TEST(StartupMonitor, init) {
StartupMonitorTest startup;
EXPECT_FALSE(startup.startup_complete());
EXPECT_EQ(startup.startup_count(), 0);
EXPECT_EQ(startup.total(), 0);
bool woken{false};
std::thread thread([&startup, &woken]() {
startup.wait_ready();
woken = true;
});
EXPECT_FALSE(woken);
EXPECT_TRUE(startup.set_total(1));
EXPECT_EQ(startup.total(), 1);
EXPECT_FALSE(woken);
EXPECT_TRUE(startup.notify_ready("manager1"));
// EXPECT_EQ(startup.startup_count(), 1); will be 0 because startup is complete
thread.join();
EXPECT_TRUE(woken);
EXPECT_TRUE(startup.startup_complete());
EXPECT_EQ(startup.total(), 0);
}
TEST(StartupMonitor, zero) {
StartupMonitorTest startup;
EXPECT_FALSE(startup.startup_complete());
EXPECT_EQ(startup.startup_count(), 0);
EXPECT_EQ(startup.total(), 0);
bool woken{false};
std::thread thread([&startup, &woken]() {
startup.wait_ready();
woken = true;
});
EXPECT_FALSE(woken);
EXPECT_TRUE(startup.set_total(0));
EXPECT_EQ(startup.total(), 0);
EXPECT_EQ(startup.startup_count(), 0);
thread.join();
EXPECT_TRUE(woken);
EXPECT_TRUE(startup.startup_complete());
EXPECT_EQ(startup.total(), 0);
}
TEST(StartupMonitor, invalidSequence) {
StartupMonitorTest startup;
EXPECT_FALSE(startup.startup_complete());
EXPECT_FALSE(startup.notify_ready("manager1")); // total not set yet
EXPECT_TRUE(startup.set_total(1));
EXPECT_EQ(startup.startup_count(), 0);
EXPECT_EQ(startup.total(), 1);
bool woken{false};
std::thread thread([&startup, &woken]() {
startup.wait_ready();
woken = true;
});
EXPECT_FALSE(startup.set_total(2)); // total already set
EXPECT_EQ(startup.total(), 1); // didn't change
EXPECT_TRUE(startup.notify_ready("manager2"));
// EXPECT_EQ(startup.startup_count(), 1); will be 0 because startup is complete
thread.join();
EXPECT_TRUE(woken);
EXPECT_TRUE(startup.startup_complete());
EXPECT_EQ(startup.total(), 0);
}
TEST(StartupMonitor, duplicateReady) {
StartupMonitorTest startup;
EXPECT_FALSE(startup.startup_complete());
EXPECT_TRUE(startup.set_total(2));
EXPECT_EQ(startup.startup_count(), 0);
EXPECT_EQ(startup.total(), 2);
bool woken{false};
std::thread thread([&startup, &woken]() {
startup.wait_ready();
woken = true;
});
EXPECT_TRUE(startup.notify_ready("manager1"));
EXPECT_EQ(startup.startup_count(), 1);
EXPECT_TRUE(startup.notify_ready("manager1")); // duplicate
EXPECT_EQ(startup.startup_count(), 1);
EXPECT_FALSE(startup.startup_complete());
EXPECT_TRUE(startup.notify_ready("manager2"));
// EXPECT_EQ(startup.startup_count(), 2); will be 0 because startup is complete
EXPECT_TRUE(startup.startup_complete());
thread.join();
EXPECT_TRUE(woken);
EXPECT_TRUE(startup.startup_complete());
EXPECT_EQ(startup.total(), 0);
}
} // namespace

View File

@@ -0,0 +1,4 @@
ev_add_module(API)
ev_add_module(EvAPI)
ev_add_module(RpcApi)
add_subdirectory(EVerestAPI)

View File

@@ -0,0 +1,22 @@
ev_add_module(auth_consumer_API)
ev_add_module(auth_token_provider_API)
ev_add_module(auth_token_validator_API)
ev_add_module(dc_external_derate_consumer_API)
ev_add_module(display_message_API)
ev_add_module(error_history_consumer_API)
ev_add_module(ev_board_support_API)
ev_add_module(evse_board_support_API)
ev_add_module(evse_manager_consumer_API)
ev_add_module(evse_security_consumer_API)
ev_add_module(external_energy_limits_consumer_API)
ev_add_module(generic_error_raiser_API)
ev_add_module(isolation_monitor_API)
ev_add_module(ocpp_consumer_API)
ev_add_module(over_voltage_monitor_API)
ev_add_module(power_supply_DC_API)
ev_add_module(powermeter_API)
ev_add_module(session_cost_consumer_API)
ev_add_module(session_cost_API)
ev_add_module(slac_API)
ev_add_module(system_API)

View File

@@ -0,0 +1 @@
EVerestAPI implementations

View File

@@ -0,0 +1,31 @@
#
# 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_compile_options(${MODULE_NAME}
PUBLIC -Wall -Wextra -pedantic -Werror=switch)
target_link_libraries(${MODULE_NAME}
PRIVATE
atomic
everest::everest_api_types
everest::everest_api_module_helpers
)
# ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1
target_sources(${MODULE_NAME}
PRIVATE
"main/generic_errorImpl.cpp"
)
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1
# insert other things like install cmds etc here
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1

View File

@@ -0,0 +1,77 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
#include "auth_consumer_API.hpp"
#include <everest_api_types/auth/API.hpp>
#include <everest_api_types/auth/codec.hpp>
#include <everest_api_types/auth/wrapper.hpp>
#include <everest_api_types/generic/codec.hpp>
#include <everest_api_types/utilities/codec.hpp>
#include <utility>
namespace module {
namespace API_types_ext = API_types::auth;
namespace API_generic = API_types::generic;
using ev_API::deserialize;
void auth_consumer_API::init() {
invoke_init(*p_main);
API_types_entry::CommunicationParameters comm_params{};
comm_params.heartbeat_period_ms = config.cfg_heartbeat_interval_ms;
comm_params.communication_check_period_s = config.cfg_communication_check_to_s;
helper.init(comm_params);
}
void auth_consumer_API::ready() {
invoke_ready(*p_main);
generate_api_cmd_withdraw_authorization();
generate_api_var_token_validation_status();
helper.generate_api_var_communication_check(&comm_check);
comm_check.start(config.cfg_communication_check_to_s);
helper.setup_heartbeat_generator(&comm_check, config.cfg_heartbeat_interval_ms);
helper.publish_ready_beacon();
}
auto auth_consumer_API::forward_api_var(std::string const& var) {
using namespace API_types_ext;
const auto topic = helper.get_topics().everest_to_extern(var);
return [this, topic](auto const& val) {
try {
auto&& external = to_external_api(val);
auto&& payload = serialize(external);
mqtt_v.publish(topic, payload);
} catch (const std::exception& e) {
EVLOG_warning << "Variable: '" << topic << "' failed with -> " << e.what();
} catch (...) {
EVLOG_warning << "Invalid data: Cannot convert internal to external or serialize it.\n" << topic;
}
};
}
void auth_consumer_API::generate_api_cmd_withdraw_authorization() {
helper.subscribe_api_topic("withdraw_authorization", [this](std::string const& data) {
API_generic::RequestReply msg;
if (deserialize(data, msg)) {
API_types_ext::WithdrawAuthorizationRequest payload;
if (deserialize(msg.payload, payload)) {
auto int_res = r_auth->call_withdraw_authorization(to_internal_api(payload));
auto ext_res = API_types_ext::to_external_api(int_res);
mqtt_v.publish(msg.replyTo, serialize(ext_res));
return true;
}
}
return false;
});
}
void auth_consumer_API::generate_api_var_token_validation_status() {
r_auth->subscribe_token_validation_status(forward_api_var("token_validation_status"));
}
} // namespace module

View File

@@ -0,0 +1,83 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
#ifndef AUTH_CONSUMER_API_HPP
#define AUTH_CONSUMER_API_HPP
//
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
// template version 2
//
#include "ld-ev.hpp"
// headers for provided interface implementations
#include <generated/interfaces/generic_error/Implementation.hpp>
// headers for required interface implementations
#include <generated/interfaces/auth/Interface.hpp>
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
// insert your custom include headers here
#include <everest_api_module_helpers/ApiHelper.hpp>
#include <everest_api_types/entrypoint/API.hpp>
namespace ev_API = everest::lib::API;
namespace API_types = ev_API::V1_0::types;
namespace API_types_entry = API_types::entrypoint;
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
namespace module {
struct Conf {
int cfg_communication_check_to_s;
int cfg_heartbeat_interval_ms;
};
class auth_consumer_API : public Everest::ModuleBase {
public:
auth_consumer_API() = delete;
auth_consumer_API(const ModuleInfo& info, Everest::MqttProvider& mqtt_provider,
std::unique_ptr<generic_errorImplBase> p_main, std::unique_ptr<authIntf> r_auth, Conf& config) :
ModuleBase(info), mqtt(mqtt_provider), p_main(std::move(p_main)), r_auth(std::move(r_auth)), config(config){};
Everest::MqttProvider& mqtt;
const std::unique_ptr<generic_errorImplBase> p_main;
const std::unique_ptr<authIntf> r_auth;
const Conf& config;
// ev@1fce4c5e-0ab8-41bb-90f7-14277703d2ac:v1
// insert your public definitions here
ev_API::Mqtt::ValidatingMqttProxy mqtt_v{mqtt};
ev_API::ApiHelper helper{info, mqtt_v, {{"auth_consumer", 1}}, get_config_service_client()};
// 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
auto forward_api_var(std::string const& var);
void generate_api_cmd_withdraw_authorization();
void generate_api_var_token_validation_status();
ev_API::CommCheckHandler<generic_errorImplBase> comm_check{"generic/CommunicationFault",
ev_API::bridge_connection_lost_message, p_main};
// 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 // AUTH_CONSUMER_API_HPP

View File

@@ -0,0 +1,11 @@
.. _everest_modules_handwritten_auth_consumer_API:
.. *******************************************
.. auth_consumer_API
.. *******************************************
The complete API specification can be found in the
``docs/source/reference/EVerest_API/auth_consumer_API.yaml``
file in the source repository, or in the `AsyncAPI HTML documentation <../../../../api/auth_consumer_API/index.html>`_ automatically generated from it.

View File

@@ -0,0 +1,16 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
#include "generic_errorImpl.hpp"
namespace module {
namespace main {
void generic_errorImpl::init() {
}
void generic_errorImpl::ready() {
}
} // namespace main
} // namespace module

View File

@@ -0,0 +1,60 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
#ifndef MAIN_GENERIC_ERROR_IMPL_HPP
#define MAIN_GENERIC_ERROR_IMPL_HPP
//
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
// template version 3
//
#include <generated/interfaces/generic_error/Implementation.hpp>
#include "../auth_consumer_API.hpp"
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
// insert your custom include headers here
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
namespace module {
namespace main {
struct Conf {};
class generic_errorImpl : public generic_errorImplBase {
public:
generic_errorImpl() = delete;
generic_errorImpl(Everest::ModuleAdapter* ev, const Everest::PtrContainer<auth_consumer_API>& mod, Conf& config) :
generic_errorImplBase(ev, "main"), mod(mod), config(config){};
// ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1
// insert your public definitions here
// ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1
protected:
// no commands defined for this interface
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
// insert your protected definitions here
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
private:
const Everest::PtrContainer<auth_consumer_API>& mod;
const Conf& config;
virtual void init() override;
virtual void ready() override;
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
// insert your private definitions here
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
};
// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1
// insert other definitions here
// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1
} // namespace main
} // namespace module
#endif // MAIN_GENERIC_ERROR_IMPL_HPP

View File

@@ -0,0 +1,26 @@
description: API for auth (consumer API)
config:
cfg_communication_check_to_s:
description: "Maximum time between two communication check events. Values <= 0 disables communication checks."
type: integer
default: 5
cfg_heartbeat_interval_ms:
description: "Interval between two heartbeat messages send by the API. Values <= 0 disable heartbeat."
type: integer
default: 1000
provides:
main:
interface: generic_error
description: "Provides generic errors for communication check"
requires:
auth:
interface: auth
enable_external_mqtt: true
metadata:
license: https://opensource.org/licenses/Apache-2.0
authors:
- Jan Christoph Habig
- Andreas Heinrich
- Florin Mihut

View File

@@ -0,0 +1,31 @@
#
# 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_compile_options(${MODULE_NAME}
PUBLIC -Wall -Wextra -pedantic -Werror=switch)
target_link_libraries(${MODULE_NAME}
PRIVATE
atomic
everest::everest_api_types
everest::everest_api_module_helpers
)
# ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1
target_sources(${MODULE_NAME}
PRIVATE
"main/auth_token_providerImpl.cpp"
)
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1
# insert other things like install cmds etc here
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1

View File

@@ -0,0 +1,51 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
#include "auth_token_provider_API.hpp"
#include <everest/logging.hpp>
#include <everest_api_types/auth/API.hpp>
#include <everest_api_types/auth/codec.hpp>
#include <everest_api_types/auth/wrapper.hpp>
#include <everest_api_types/generic/codec.hpp>
#include <everest_api_types/utilities/codec.hpp>
namespace module {
namespace API_types_ext = API_types::auth;
namespace API_generic = API_types::generic;
using ev_API::deserialize;
void auth_token_provider_API::init() {
invoke_init(*p_main);
API_types_entry::CommunicationParameters comm_params{};
comm_params.heartbeat_period_ms = config.cfg_heartbeat_interval_ms;
comm_params.communication_check_period_s = config.cfg_communication_check_to_s;
helper.init(comm_params);
}
void auth_token_provider_API::ready() {
invoke_ready(*p_main);
generate_api_var_provided_token();
helper.generate_api_var_communication_check(&comm_check);
comm_check.start(config.cfg_communication_check_to_s);
helper.setup_heartbeat_generator(&comm_check, config.cfg_heartbeat_interval_ms);
helper.publish_ready_beacon();
}
void auth_token_provider_API::generate_api_var_provided_token() {
helper.subscribe_api_topic("provided_token", [this](const std::string& data) {
API_types_ext::ProvidedIdToken payload;
if (deserialize(data, payload)) {
p_main->publish_provided_token(to_internal_api(payload));
return true;
}
return false;
});
}
} // namespace module

View File

@@ -0,0 +1,75 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
#ifndef AUTH_TOKEN_PROVIDER_API_HPP
#define AUTH_TOKEN_PROVIDER_API_HPP
//
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
// template version 2
//
#include "ld-ev.hpp"
// headers for provided interface implementations
#include <generated/interfaces/auth_token_provider/Implementation.hpp>
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
// insert your custom include headers here
#include <everest_api_module_helpers/ApiHelper.hpp>
#include <everest_api_types/entrypoint/API.hpp>
namespace ev_API = everest::lib::API;
namespace API_types = ev_API::V1_0::types;
namespace API_types_entry = API_types::entrypoint;
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
namespace module {
struct Conf {
int cfg_communication_check_to_s;
int cfg_heartbeat_interval_ms;
};
class auth_token_provider_API : public Everest::ModuleBase {
public:
auth_token_provider_API() = delete;
auth_token_provider_API(const ModuleInfo& info, Everest::MqttProvider& mqtt_provider,
std::unique_ptr<auth_token_providerImplBase> p_main, Conf& config) :
ModuleBase(info), mqtt(mqtt_provider), p_main(std::move(p_main)), config(config){};
Everest::MqttProvider& mqtt;
const std::unique_ptr<auth_token_providerImplBase> p_main;
const Conf& config;
// ev@1fce4c5e-0ab8-41bb-90f7-14277703d2ac:v1
// insert your public definitions here
ev_API::Mqtt::ValidatingMqttProxy mqtt_v{mqtt};
ev_API::ApiHelper helper{info, mqtt_v, {{"auth_token_provider", 1}}, get_config_service_client()};
// 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
void generate_api_var_provided_token();
ev_API::CommCheckHandler<auth_token_providerImplBase> comm_check{"generic/CommunicationFault",
ev_API::bridge_connection_lost_message, p_main};
// 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 // AUTH_TOKEN_PROVIDER_API_HPP

View File

@@ -0,0 +1,11 @@
.. _everest_modules_handwritten_auth_token_provider_API:
.. *******************************************
.. auth_token_provider_API
.. *******************************************
The complete API specification can be found in the
``docs/source/reference/EVerest_API/auth_token_provider_API.yaml``
file in the source repository, or in the `AsyncAPI HTML documentation <../../../../api/auth_token_provider_API/index.html>`_ automatically generated from it.

View File

@@ -0,0 +1,16 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
#include "auth_token_providerImpl.hpp"
namespace module {
namespace main {
void auth_token_providerImpl::init() {
}
void auth_token_providerImpl::ready() {
}
} // namespace main
} // namespace module

View File

@@ -0,0 +1,61 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
#ifndef MAIN_AUTH_TOKEN_PROVIDER_IMPL_HPP
#define MAIN_AUTH_TOKEN_PROVIDER_IMPL_HPP
//
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
// template version 3
//
#include <generated/interfaces/auth_token_provider/Implementation.hpp>
#include "../auth_token_provider_API.hpp"
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
// insert your custom include headers here
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
namespace module {
namespace main {
struct Conf {};
class auth_token_providerImpl : public auth_token_providerImplBase {
public:
auth_token_providerImpl() = delete;
auth_token_providerImpl(Everest::ModuleAdapter* ev, const Everest::PtrContainer<auth_token_provider_API>& mod,
Conf& config) :
auth_token_providerImplBase(ev, "main"), mod(mod), config(config){};
// ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1
// insert your public definitions here
// ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1
protected:
// no commands defined for this interface
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
// insert your protected definitions here
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
private:
const Everest::PtrContainer<auth_token_provider_API>& mod;
const Conf& config;
virtual void init() override;
virtual void ready() override;
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
// insert your private definitions here
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
};
// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1
// insert other definitions here
// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1
} // namespace main
} // namespace module
#endif // MAIN_AUTH_TOKEN_PROVIDER_IMPL_HPP

View File

@@ -0,0 +1,24 @@
description: API for auth token provider
config:
cfg_communication_check_to_s:
description: "Maximum time between two communication check events. Values <= 0 disables communication checks."
type: integer
default: 5
cfg_heartbeat_interval_ms:
description: "Interval between two heartbeat messages send by the API. Values <= 0 disable heartbeat."
type: integer
default: 1000
provides:
main:
interface: auth_token_provider
description: "Allows API clients to provide auth tokens."
enable_external_mqtt: true
metadata:
license: https://opensource.org/licenses/Apache-2.0
authors:
- James Chapman
- Jan Christoph Habig
- Andreas Heinrich
- Florin Mihut

View File

@@ -0,0 +1,31 @@
#
# 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_compile_options(${MODULE_NAME}
PUBLIC -Wall -Wextra -pedantic -Werror=switch)
target_link_libraries(${MODULE_NAME}
PRIVATE
atomic
everest::everest_api_types
everest::everest_api_module_helpers
)
# ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1
target_sources(${MODULE_NAME}
PRIVATE
"main/auth_token_validatorImpl.cpp"
)
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1
# insert other things like install cmds etc here
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1

View File

@@ -0,0 +1,51 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
#include "auth_token_validator_API.hpp"
#include <everest_api_types/auth/API.hpp>
#include <everest_api_types/auth/codec.hpp>
#include <everest_api_types/auth/wrapper.hpp>
#include <everest_api_types/generic/codec.hpp>
#include <everest_api_types/utilities/codec.hpp>
#include <everest/logging.hpp>
namespace module {
namespace API_types_ext = API_types::auth;
namespace API_generic = API_types::generic;
using ev_API::deserialize;
void auth_token_validator_API::init() {
invoke_init(*p_main);
API_types_entry::CommunicationParameters comm_params{};
comm_params.heartbeat_period_ms = config.cfg_heartbeat_interval_ms;
comm_params.communication_check_period_s = config.cfg_communication_check_to_s;
comm_params.request_reply_timeout_s = config.cfg_request_reply_to_s;
helper.init(comm_params);
}
void auth_token_validator_API::ready() {
invoke_ready(*p_main);
generate_api_var_validation_result_update();
helper.generate_api_var_communication_check(&comm_check);
comm_check.start(config.cfg_communication_check_to_s);
helper.setup_heartbeat_generator(&comm_check, config.cfg_heartbeat_interval_ms);
helper.publish_ready_beacon();
}
void auth_token_validator_API::generate_api_var_validation_result_update() {
helper.subscribe_api_topic("validate_result_update", [=](std::string const& data) {
API_types_ext::ValidationResultUpdate payload;
if (deserialize(data, payload)) {
p_main->publish_validate_result_update(to_internal_api(payload));
return true;
}
return false;
});
}
} // namespace module

View File

@@ -0,0 +1,76 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
#ifndef AUTH_TOKEN_VALIDATOR_API_HPP
#define AUTH_TOKEN_VALIDATOR_API_HPP
//
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
// template version 2
//
#include "ld-ev.hpp"
// headers for provided interface implementations
#include <generated/interfaces/auth_token_validator/Implementation.hpp>
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
// insert your custom include headers here
#include <everest_api_module_helpers/ApiHelper.hpp>
#include <everest_api_types/entrypoint/API.hpp>
namespace ev_API = everest::lib::API;
namespace API_types = ev_API::V1_0::types;
namespace API_types_entry = API_types::entrypoint;
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
namespace module {
struct Conf {
int cfg_communication_check_to_s;
int cfg_heartbeat_interval_ms;
int cfg_request_reply_to_s;
};
class auth_token_validator_API : public Everest::ModuleBase {
public:
auth_token_validator_API() = delete;
auth_token_validator_API(const ModuleInfo& info, Everest::MqttProvider& mqtt_provider,
std::unique_ptr<auth_token_validatorImplBase> p_main, Conf& config) :
ModuleBase(info), mqtt(mqtt_provider), p_main(std::move(p_main)), config(config){};
Everest::MqttProvider& mqtt;
const std::unique_ptr<auth_token_validatorImplBase> p_main;
const Conf& config;
// ev@1fce4c5e-0ab8-41bb-90f7-14277703d2ac:v1
// insert your public definitions here
ev_API::Mqtt::ValidatingMqttProxy mqtt_v{mqtt};
ev_API::ApiHelper helper{info, mqtt_v, {{"auth_token_validator", 1}}, get_config_service_client()};
// 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
void generate_api_var_validation_result_update();
ev_API::CommCheckHandler<auth_token_validatorImplBase> comm_check{"generic/CommunicationFault",
ev_API::bridge_connection_lost_message, p_main};
// 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 // AUTH_TOKEN_VALIDATOR_API_HPP

View File

@@ -0,0 +1,11 @@
.. _everest_modules_handwritten_auth_token_validator_API:
.. *******************************************
.. auth_token_validator_API
.. *******************************************
The complete API specification can be found in the
``docs/source/reference/EVerest_API/auth_token_validator_API.yaml``
file in the source repository, or in the `AsyncAPI HTML documentation <../../../../api/auth_token_validator_API/index.html>`_ automatically generated from it.

View File

@@ -0,0 +1,46 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
#include "auth_token_validatorImpl.hpp"
#include <everest_api_types/auth/API.hpp>
#include <everest_api_types/auth/codec.hpp>
#include <everest_api_types/auth/json_codec.hpp>
#include <everest_api_types/auth/wrapper.hpp>
#include <everest_api_types/utilities/AsyncApiRequestReply.hpp>
#include <generated/types/authorization.hpp>
namespace API_types_ext = ev_API::V1_0::types::auth;
namespace module {
namespace main {
void auth_token_validatorImpl::init() {
timeout_s = mod->config.cfg_request_reply_to_s;
}
void auth_token_validatorImpl::ready() {
}
template <class T, class ReqT>
auto auth_token_validatorImpl::generic_request_reply(T const& default_value, ReqT const& request,
std::string const& topic) {
using namespace API_types_ext;
using ExtT = decltype(to_external_api(std::declval<T>()));
auto result = ev_API::request_reply_handler<ExtT>(mod->mqtt_v, mod->helper.get_topics(), request, topic, timeout_s);
if (!result) {
return default_value;
}
return result.value();
}
types::authorization::ValidationResult
auth_token_validatorImpl::handle_validate_token(types::authorization::ProvidedIdToken& provided_token) {
static const types::authorization::ValidationResult default_response{
types::authorization::AuthorizationStatus::Invalid, {}, {}, {}, {}, {}, {}, {}};
return generic_request_reply(default_response, API_types_ext::to_external_api(provided_token), "validate_token");
}
} // namespace main
} // namespace module

View File

@@ -0,0 +1,67 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
#ifndef MAIN_AUTH_TOKEN_VALIDATOR_IMPL_HPP
#define MAIN_AUTH_TOKEN_VALIDATOR_IMPL_HPP
//
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
// template version 3
//
#include <generated/interfaces/auth_token_validator/Implementation.hpp>
#include "../auth_token_validator_API.hpp"
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
// insert your custom include headers here
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
namespace module {
namespace main {
struct Conf {};
class auth_token_validatorImpl : public auth_token_validatorImplBase {
public:
auth_token_validatorImpl() = delete;
auth_token_validatorImpl(Everest::ModuleAdapter* ev, const Everest::PtrContainer<auth_token_validator_API>& mod,
Conf& config) :
auth_token_validatorImplBase(ev, "main"), mod(mod), config(config){};
// ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1
// insert your public definitions here
// ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1
protected:
// command handler functions (virtual)
virtual types::authorization::ValidationResult
handle_validate_token(types::authorization::ProvidedIdToken& provided_token) override;
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
// insert your protected definitions here
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
private:
const Everest::PtrContainer<auth_token_validator_API>& mod;
const Conf& config;
virtual void init() override;
virtual void ready() override;
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
// insert your private definitions here
template <class T, class ReqT>
auto generic_request_reply(T const& default_value, ReqT const& request, std::string const& topic);
int timeout_s{5};
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
};
// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1
// insert other definitions here
// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1
} // namespace main
} // namespace module
#endif // MAIN_AUTH_TOKEN_VALIDATOR_IMPL_HPP

View File

@@ -0,0 +1,30 @@
description: API for auth token validator
config:
cfg_communication_check_to_s:
description: "Maximum time between two communication check events. Values <= 0 disables communication checks."
type: integer
default: 5
cfg_heartbeat_interval_ms:
description: "Interval between two heartbeat messages send by the API. Values <= 0 disable heartbeat."
type: integer
default: 1000
cfg_request_reply_to_s:
description: "Maximum time between request and reply. After timeout the request is answered with a default response."
type: integer
default: 550
minimum: 1
maximum: 550
provides:
main:
interface: auth_token_validator
description: "Allows API clients to validate auth tokens."
enable_external_mqtt: true
metadata:
license: https://opensource.org/licenses/Apache-2.0
authors:
- James Chapman
- Jan Christoph Habig
- Andreas Heinrich
- Florin Mihut

View File

@@ -0,0 +1,31 @@
#
# 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_compile_options(${MODULE_NAME}
PUBLIC -Wall -Wextra -pedantic -Werror=switch)
target_link_libraries(${MODULE_NAME}
PRIVATE
atomic
everest::everest_api_types
everest::everest_api_module_helpers
)
# ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1
target_sources(${MODULE_NAME}
PRIVATE
"generic_error/generic_errorImpl.cpp"
)
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1
# insert other things like install cmds etc here
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1

View File

@@ -0,0 +1,75 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
#include "dc_external_derate_consumer_API.hpp"
#include "everest_api_types/dc_external_derate/API.hpp"
#include "everest_api_types/dc_external_derate/codec.hpp"
#include "everest_api_types/dc_external_derate/wrapper.hpp"
#include "everest_api_types/generic/codec.hpp"
#include "everest_api_types/generic/string.hpp"
#include "everest_api_types/utilities/codec.hpp"
namespace module {
namespace API_types_ext = API_types::dc_external_derate;
namespace API_generic = API_types::generic;
using ev_API::deserialize;
namespace {
double to_external_api(double val) {
return val;
}
} // namespace
void dc_external_derate_consumer_API::init() {
invoke_init(*p_generic_error);
API_types_entry::CommunicationParameters comm_params{};
comm_params.heartbeat_period_ms = config.cfg_heartbeat_interval_ms;
comm_params.communication_check_period_s = config.cfg_communication_check_to_s;
helper.init(comm_params);
}
void dc_external_derate_consumer_API::ready() {
invoke_ready(*p_generic_error);
generate_api_cmd_set_external_derating();
generate_api_var_plug_temperature_C();
helper.generate_api_var_communication_check(&comm_check);
comm_check.start(config.cfg_communication_check_to_s);
helper.setup_heartbeat_generator(&comm_check, config.cfg_heartbeat_interval_ms);
helper.publish_ready_beacon();
}
auto dc_external_derate_consumer_API::forward_api_var(std::string const& var) {
using namespace API_types_ext;
using namespace API_generic;
const auto topic = helper.get_topics().everest_to_extern(var);
return [this, topic](auto const& val) {
try {
auto&& external = to_external_api(val);
auto&& payload = serialize(external);
mqtt_v.publish(topic, payload);
} catch (const std::exception& e) {
EVLOG_warning << "Variable: '" << topic << "' failed with -> " << e.what();
} catch (...) {
EVLOG_warning << "Invalid data: Cannot convert internal to external or serialize it.\n" << topic;
}
};
}
void dc_external_derate_consumer_API::generate_api_var_plug_temperature_C() {
r_derate->subscribe_plug_temperature_C(forward_api_var("plug_temperature_C"));
}
void dc_external_derate_consumer_API::generate_api_cmd_set_external_derating() {
helper.subscribe_api_topic("set_external_derating", [=](std::string const& data) {
API_types_ext::ExternalDerating external;
if (deserialize(data, external)) {
r_derate->call_set_external_derating(to_internal_api(external));
return true;
}
return false;
});
}
} // namespace module

View File

@@ -0,0 +1,88 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
#ifndef DC_EXTERNAL_DERATE_CONSUMER_API_HPP
#define DC_EXTERNAL_DERATE_CONSUMER_API_HPP
//
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
// template version 2
//
#include "ld-ev.hpp"
// headers for provided interface implementations
#include <generated/interfaces/generic_error/Implementation.hpp>
// headers for required interface implementations
#include <generated/interfaces/dc_external_derate/Interface.hpp>
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
// insert your custom include headers here
#include <everest_api_module_helpers/ApiHelper.hpp>
#include <everest_api_types/entrypoint/API.hpp>
namespace ev_API = everest::lib::API;
namespace API_types = ev_API::V1_0::types;
namespace API_types_entry = API_types::entrypoint;
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
namespace module {
struct Conf {
int cfg_communication_check_to_s;
int cfg_heartbeat_interval_ms;
};
class dc_external_derate_consumer_API : public Everest::ModuleBase {
public:
dc_external_derate_consumer_API() = delete;
dc_external_derate_consumer_API(const ModuleInfo& info, Everest::MqttProvider& mqtt_provider,
std::unique_ptr<generic_errorImplBase> p_generic_error,
std::unique_ptr<dc_external_derateIntf> r_derate, Conf& config) :
ModuleBase(info),
mqtt(mqtt_provider),
p_generic_error(std::move(p_generic_error)),
r_derate(std::move(r_derate)),
config(config){};
Everest::MqttProvider& mqtt;
const std::unique_ptr<generic_errorImplBase> p_generic_error;
const std::unique_ptr<dc_external_derateIntf> r_derate;
const Conf& config;
// ev@1fce4c5e-0ab8-41bb-90f7-14277703d2ac:v1
// insert your public definitions here
ev_API::Mqtt::ValidatingMqttProxy mqtt_v{mqtt};
ev_API::ApiHelper helper{info, mqtt_v, {{"dc_external_derate_consumer", 1}}, get_config_service_client()};
// 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
auto forward_api_var(std::string const& var);
void generate_api_var_plug_temperature_C();
void generate_api_cmd_set_external_derating();
ev_API::CommCheckHandler<generic_errorImplBase> comm_check{"generic/CommunicationFault",
ev_API::bridge_connection_lost_message, p_generic_error};
// 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 // DC_EXTERNAL_DERATE_CONSUMER_API_HPP

View File

@@ -0,0 +1,11 @@
.. _everest_modules_handwritten_dc_external_derate_consumer_API:
.. *******************************************
.. dc_external_derate_consumer_API
.. *******************************************
The complete API specification can be found in the
``docs/source/reference/EVerest_API/dc_external_derate_consumer_API.yaml``
file in the source repository, or in the `AsyncAPI HTML documentation <../../../../api/dc_external_derate_consumer_API/index.html>`_ automatically generated from it.

View File

@@ -0,0 +1,16 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
#include "generic_errorImpl.hpp"
namespace module {
namespace generic_error {
void generic_errorImpl::init() {
}
void generic_errorImpl::ready() {
}
} // namespace generic_error
} // namespace module

View File

@@ -0,0 +1,61 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
#ifndef GENERIC_ERROR_GENERIC_ERROR_IMPL_HPP
#define GENERIC_ERROR_GENERIC_ERROR_IMPL_HPP
//
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
// template version 3
//
#include <generated/interfaces/generic_error/Implementation.hpp>
#include "../dc_external_derate_consumer_API.hpp"
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
// insert your custom include headers here
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
namespace module {
namespace generic_error {
struct Conf {};
class generic_errorImpl : public generic_errorImplBase {
public:
generic_errorImpl() = delete;
generic_errorImpl(Everest::ModuleAdapter* ev, const Everest::PtrContainer<dc_external_derate_consumer_API>& mod,
Conf& config) :
generic_errorImplBase(ev, "generic_error"), mod(mod), config(config){};
// ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1
// insert your public definitions here
// ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1
protected:
// no commands defined for this interface
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
// insert your protected definitions here
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
private:
const Everest::PtrContainer<dc_external_derate_consumer_API>& mod;
const Conf& config;
virtual void init() override;
virtual void ready() override;
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
// insert your private definitions here
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
};
// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1
// insert other definitions here
// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1
} // namespace generic_error
} // namespace module
#endif // GENERIC_ERROR_GENERIC_ERROR_IMPL_HPP

View File

@@ -0,0 +1,25 @@
description: API for derating DC power supplies
config:
cfg_communication_check_to_s:
description: "Maximum time between two communication check events. Values <= 0 disables communication checks"
type: integer
default: 5
cfg_heartbeat_interval_ms:
description: "Interval between two heartbeat messages send by the API. Values <= 0 disable heartbeat"
type: integer
default: 1000
provides:
generic_error:
interface: generic_error
description: "Providing generic errors for communication check"
requires:
derate:
interface: dc_external_derate
enable_external_mqtt: true
metadata:
license: https://opensource.org/licenses/Apache-2.0
authors:
- Jan Christoph Habig

View File

@@ -0,0 +1,32 @@
#
# 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_compile_options(${MODULE_NAME}
PUBLIC -Wall -Wextra -pedantic -Werror=switch)
target_link_libraries(${MODULE_NAME}
PRIVATE
atomic
everest::everest_api_types
everest::everest_api_module_helpers
)
# ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1
target_sources(${MODULE_NAME}
PRIVATE
"main/display_messageImpl.cpp"
"generic_error/generic_errorImpl.cpp"
)
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1
# insert other things like install cmds etc here
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1

View File

@@ -0,0 +1,42 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
#include "display_message_API.hpp"
#include <everest_api_types/display_message/API.hpp>
#include <everest_api_types/display_message/codec.hpp>
#include <everest_api_types/display_message/wrapper.hpp>
#include <everest_api_types/generic/codec.hpp>
#include <everest_api_types/utilities/codec.hpp>
#include <generated/types/display_message.hpp>
#include <string>
#include <utility>
namespace module {
namespace API_types_ext = API_types::display_message;
namespace API_generic = API_types::generic;
using ev_API::deserialize;
void display_message_API::init() {
invoke_init(*p_main);
invoke_init(*p_generic_error);
API_types_entry::CommunicationParameters comm_params{};
comm_params.heartbeat_period_ms = config.cfg_heartbeat_interval_ms;
comm_params.communication_check_period_s = config.cfg_communication_check_to_s;
comm_params.request_reply_timeout_s = config.cfg_request_reply_to_s;
helper.init(comm_params);
}
void display_message_API::ready() {
invoke_ready(*p_main);
invoke_ready(*p_generic_error);
helper.generate_api_var_communication_check(&comm_check);
comm_check.start(config.cfg_communication_check_to_s);
helper.setup_heartbeat_generator(&comm_check, config.cfg_heartbeat_interval_ms);
helper.publish_ready_beacon();
}
} // namespace module

View File

@@ -0,0 +1,82 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
#ifndef DISPLAY_MESSAGE_API_HPP
#define DISPLAY_MESSAGE_API_HPP
//
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
// template version 2
//
#include "ld-ev.hpp"
// headers for provided interface implementations
#include <generated/interfaces/display_message/Implementation.hpp>
#include <generated/interfaces/generic_error/Implementation.hpp>
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
// insert your custom include headers here
#include <everest_api_module_helpers/ApiHelper.hpp>
#include <everest_api_types/entrypoint/API.hpp>
namespace ev_API = everest::lib::API;
namespace API_types = ev_API::V1_0::types;
namespace API_types_entry = API_types::entrypoint;
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
namespace module {
struct Conf {
int cfg_communication_check_to_s;
int cfg_heartbeat_interval_ms;
int cfg_request_reply_to_s;
};
class display_message_API : public Everest::ModuleBase {
public:
display_message_API() = delete;
display_message_API(const ModuleInfo& info, Everest::MqttProvider& mqtt_provider,
std::unique_ptr<display_messageImplBase> p_main,
std::unique_ptr<generic_errorImplBase> p_generic_error, Conf& config) :
ModuleBase(info),
mqtt(mqtt_provider),
p_main(std::move(p_main)),
p_generic_error(std::move(p_generic_error)),
config(config){};
Everest::MqttProvider& mqtt;
const std::unique_ptr<display_messageImplBase> p_main;
const std::unique_ptr<generic_errorImplBase> p_generic_error;
const Conf& config;
// ev@1fce4c5e-0ab8-41bb-90f7-14277703d2ac:v1
// insert your public definitions here
const ev_API::Topics& get_topics() const;
ev_API::Mqtt::ValidatingMqttProxy mqtt_v{mqtt};
ev_API::ApiHelper helper{info, mqtt_v, {{"display_message", 1}}, get_config_service_client()};
// 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
ev_API::CommCheckHandler<generic_errorImplBase> comm_check{"generic/CommunicationFault",
ev_API::bridge_connection_lost_message, p_generic_error};
// 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 // DISPLAY_MESSAGE_API_HPP

View File

@@ -0,0 +1,11 @@
.. _everest_modules_handwritten_display_message_API:
.. *******************************************
.. display_message_API
.. *******************************************
The complete API specification can be found in the
``docs/source/reference/EVerest_API/display_message_API.yaml``
file in the source repository, or in the `AsyncAPI HTML documentation <../../../../api/display_message_API/index.html>`_ automatically generated from it.

View File

@@ -0,0 +1,15 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
#include "generic_errorImpl.hpp"
namespace module {
namespace generic_error {
void generic_errorImpl::init() {
}
void generic_errorImpl::ready() {
}
} // namespace generic_error
} // namespace module

View File

@@ -0,0 +1,60 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
#ifndef GENERIC_ERROR_GENERIC_ERROR_IMPL_HPP
#define GENERIC_ERROR_GENERIC_ERROR_IMPL_HPP
//
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
// template version 3
//
#include <generated/interfaces/generic_error/Implementation.hpp>
#include "../display_message_API.hpp"
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
// insert your custom include headers here
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
namespace module {
namespace generic_error {
struct Conf {};
class generic_errorImpl : public generic_errorImplBase {
public:
generic_errorImpl() = delete;
generic_errorImpl(Everest::ModuleAdapter* ev, const Everest::PtrContainer<display_message_API>& mod, Conf& config) :
generic_errorImplBase(ev, "generic_error"), mod(mod), config(config){};
// ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1
// insert your public definitions here
// ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1
protected:
// no commands defined for this interface
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
// insert your protected definitions here
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
private:
const Everest::PtrContainer<display_message_API>& mod;
const Conf& config;
virtual void init() override;
virtual void ready() override;
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
// insert your private definitions here
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
};
// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1
// insert other definitions here
// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1
} // namespace generic_error
} // namespace module
#endif // GENERIC_ERROR_GENERIC_ERROR_IMPL_HPP

View File

@@ -0,0 +1,63 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
#include "display_messageImpl.hpp"
#include <everest_api_types/display_message/API.hpp>
#include <everest_api_types/display_message/codec.hpp>
#include <everest_api_types/display_message/json_codec.hpp>
#include <everest_api_types/display_message/wrapper.hpp>
#include <everest_api_types/system/wrapper.hpp>
#include <everest_api_types/utilities/AsyncApiRequestReply.hpp>
#include <generated/types/display_message.hpp>
namespace API_types_ext = ev_API::V1_0::types::display_message;
namespace module {
namespace main {
void display_messageImpl::init() {
timeout_s = mod->config.cfg_request_reply_to_s;
}
void display_messageImpl::ready() {
}
template <class T, class ReqT>
auto display_messageImpl::generic_request_reply(T const& default_value, ReqT const& request, std::string const& topic) {
using namespace API_types_ext;
using ExtT = decltype(to_external_api(std::declval<T>()));
auto result = ev_API::request_reply_handler<ExtT>(mod->mqtt_v, mod->helper.get_topics(), request, topic, timeout_s);
if (!result) {
return default_value;
}
return result.value();
}
types::display_message::SetDisplayMessageResponse
display_messageImpl::handle_set_display_message(std::vector<types::display_message::DisplayMessage>& request) {
static const types::display_message::SetDisplayMessageResponse default_response{
types::display_message::DisplayMessageStatusEnum::UnknownTransaction, {}};
std::vector<API_types_ext::DisplayMessage> messages;
for (const auto& message : request) {
messages.push_back(API_types_ext::to_external_api(message));
}
return generic_request_reply(default_response, messages, "set_display_message");
}
types::display_message::GetDisplayMessageResponse
display_messageImpl::handle_get_display_messages(types::display_message::GetDisplayMessageRequest& request) {
types::display_message::GetDisplayMessageResponse default_response;
return generic_request_reply(default_response, API_types_ext::to_external_api(request), "get_display_message");
}
types::display_message::ClearDisplayMessageResponse
display_messageImpl::handle_clear_display_message(types::display_message::ClearDisplayMessageRequest& request) {
types::display_message::ClearDisplayMessageResponse default_response{
types::display_message::ClearMessageResponseEnum::Unknown, {}};
return generic_request_reply(default_response, API_types_ext::to_external_api(request), "clear_display_message");
}
} // namespace main
} // namespace module

View File

@@ -0,0 +1,72 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
#ifndef MAIN_DISPLAY_MESSAGE_IMPL_HPP
#define MAIN_DISPLAY_MESSAGE_IMPL_HPP
//
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
// template version 3
//
#include <generated/interfaces/display_message/Implementation.hpp>
#include "../display_message_API.hpp"
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
// insert your custom include headers here
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
namespace module {
namespace main {
struct Conf {};
class display_messageImpl : public display_messageImplBase {
public:
display_messageImpl() = delete;
display_messageImpl(Everest::ModuleAdapter* ev, const Everest::PtrContainer<display_message_API>& mod,
Conf& config) :
display_messageImplBase(ev, "main"), mod(mod), config(config){};
// ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1
// insert your public definitions here
// ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1
protected:
// command handler functions (virtual)
virtual types::display_message::SetDisplayMessageResponse
handle_set_display_message(std::vector<types::display_message::DisplayMessage>& request) override;
virtual types::display_message::GetDisplayMessageResponse
handle_get_display_messages(types::display_message::GetDisplayMessageRequest& request) override;
virtual types::display_message::ClearDisplayMessageResponse
handle_clear_display_message(types::display_message::ClearDisplayMessageRequest& request) override;
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
// insert your protected definitions here
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
private:
const Everest::PtrContainer<display_message_API>& mod;
const Conf& config;
virtual void init() override;
virtual void ready() override;
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
// insert your private definitions here
template <class T, class ReqT>
auto generic_request_reply(T const& default_value, ReqT const& request, std::string const& topic);
int timeout_s{5};
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
};
// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1
// insert other definitions here
// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1
} // namespace main
} // namespace module
#endif // MAIN_DISPLAY_MESSAGE_IMPL_HPP

View File

@@ -0,0 +1,31 @@
description: API for display messages
config:
cfg_communication_check_to_s:
description: "Maximum time between two communication check events. Values <= 0 disables communication checks."
type: integer
default: 5
cfg_heartbeat_interval_ms:
description: "Interval between two heartbeat messages send by the API. Values <= 0 disable heartbeat."
type: integer
default: 1000
cfg_request_reply_to_s:
description: "Maximum time between request and reply. After timeout the request is answered with a default response."
type: integer
default: 550
minimum: 1
maximum: 550
provides:
main:
interface: display_message
description: "Allows API clients to receive messages to be displayed."
generic_error:
interface: generic_error
description: "Provides errors types for module communication status."
enable_external_mqtt: true
metadata:
license: https://opensource.org/licenses/Apache-2.0
authors:
- Jan Christoph Habig
- Florin Mihut

View File

@@ -0,0 +1,40 @@
#
# 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_compile_options(${MODULE_NAME}
PUBLIC -Wall -Wextra -pedantic -Werror=switch)
target_link_libraries(${MODULE_NAME}
PRIVATE
atomic
everest::everest_api_types
everest::everest_api_module_helpers
)
target_include_directories(${MODULE_NAME}
PRIVATE
"include"
)
# ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1
target_sources(${MODULE_NAME}
PRIVATE
"main/generic_errorImpl.cpp"
)
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1
# insert other things like install cmds etc here
target_sources(${MODULE_NAME}
PRIVATE
"src/error_wrapper.cpp"
)
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1

View File

@@ -0,0 +1,11 @@
.. _everest_modules_handwritten_error_history_consumer_API:
.. *******************************************
.. error_history_consumer_API
.. *******************************************
The complete API specification can be found in the
``docs/source/reference/EVerest_API/error_history_consumer_API.yaml``
file in the source repository, or in the `AsyncAPI HTML documentation <../../../../api/error_history_consumer_API/index.html>`_ automatically generated from it.

View File

@@ -0,0 +1,104 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
#include "error_history_consumer_API.hpp"
#include "error_wrapper.hpp"
#include <everest_api_types/error_history/API.hpp>
#include <everest_api_types/error_history/codec.hpp>
#include <everest_api_types/error_history/wrapper.hpp>
#include <everest_api_types/generic/codec.hpp>
#include <everest_api_types/utilities/codec.hpp>
#include <map>
#include <utility>
#include <vector>
#include <generated/types/error_history.hpp>
namespace module {
namespace API_generic = API_types::generic;
using ev_API::deserialize;
void error_history_consumer_API::init() {
invoke_init(*p_main);
API_types_entry::CommunicationParameters comm_params{};
comm_params.heartbeat_period_ms = config.cfg_heartbeat_interval_ms;
comm_params.communication_check_period_s = config.cfg_communication_check_to_s;
helper.init(comm_params);
}
void error_history_consumer_API::ready() {
invoke_ready(*p_main);
generate_api_cmd_active_errors();
generate_api_cmd_get_errors();
generate_api_var_error_events();
helper.generate_api_var_communication_check(&comm_check);
comm_check.start(config.cfg_communication_check_to_s);
helper.setup_heartbeat_generator(&comm_check, config.cfg_heartbeat_interval_ms);
helper.publish_ready_beacon();
}
auto error_history_consumer_API::forward_api_var(std::string const& var) {
using namespace API_types_ext;
const auto topic = helper.get_topics().everest_to_extern(var);
return [this, topic](auto const& val) {
try {
auto&& external = to_external_api(val);
auto&& payload = serialize(external);
mqtt_v.publish(topic, payload);
} catch (const std::exception& e) {
EVLOG_warning << "Variable: '" << topic << "' failed with -> " << e.what();
} catch (...) {
EVLOG_warning << "Invalid data: Cannot convert internal to external or serialize it.\n" << topic;
}
};
}
void error_history_consumer_API::generate_api_cmd_active_errors() {
using namespace API_types_ext;
helper.subscribe_api_topic("active_errors", [=](std::string const& data) {
API_generic::RequestReply msg;
if (deserialize(data, msg)) {
types::error_history::FilterArguments&& filter{};
filter.state_filter = types::error_history::State::Active;
auto active_errors = r_error_history->call_get_errors(std::move(filter));
auto reply = to_external_api(active_errors);
mqtt_v.publish(msg.replyTo, serialize(reply));
return true;
}
return false;
});
}
void error_history_consumer_API::generate_api_cmd_get_errors() {
using namespace API_types_ext;
helper.subscribe_api_topic("get_errors", [=](std::string const& data) {
API_generic::RequestReply msg;
if (deserialize(data, msg)) {
API_types_ext::FilterArguments_External payload;
if (deserialize(msg.payload, payload)) {
auto errors = r_error_history->call_get_errors(to_internal_api(payload));
auto reply = to_external_api(errors);
mqtt_v.publish(msg.replyTo, serialize(reply));
return true;
}
}
return false;
});
}
void error_history_consumer_API::generate_api_var_error_events() {
auto convert = [](auto const& ftor) {
return [ftor](auto&& elem) { return ftor(error_converter::framework_to_internal_api(elem)); };
};
subscribe_global_all_errors(convert(forward_api_var("error_raised")), convert(forward_api_var("error_cleared")));
}
} // namespace module

View File

@@ -0,0 +1,91 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
#ifndef ERROR_HISTORY_CONSUMER_API_HPP
#define ERROR_HISTORY_CONSUMER_API_HPP
//
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
// template version 2
//
#include "ld-ev.hpp"
// headers for provided interface implementations
#include <generated/interfaces/generic_error/Implementation.hpp>
// headers for required interface implementations
#include <generated/interfaces/error_history/Interface.hpp>
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
// insert your custom include headers here
#include <everest_api_module_helpers/ApiHelper.hpp>
#include <everest_api_types/entrypoint/API.hpp>
#include <everest_api_types/error_history/API.hpp>
namespace ev_API = everest::lib::API;
namespace API_types = ev_API::V1_0::types;
namespace API_types_ext = API_types::error_history;
namespace API_types_entry = API_types::entrypoint;
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
namespace module {
struct Conf {
int cfg_communication_check_to_s;
int cfg_heartbeat_interval_ms;
};
class error_history_consumer_API : public Everest::ModuleBase {
public:
error_history_consumer_API() = delete;
error_history_consumer_API(const ModuleInfo& info, Everest::MqttProvider& mqtt_provider,
std::unique_ptr<generic_errorImplBase> p_main,
std::unique_ptr<error_historyIntf> r_error_history, Conf& config) :
ModuleBase(info),
mqtt(mqtt_provider),
p_main(std::move(p_main)),
r_error_history(std::move(r_error_history)),
config(config){};
Everest::MqttProvider& mqtt;
const std::unique_ptr<generic_errorImplBase> p_main;
const std::unique_ptr<error_historyIntf> r_error_history;
const Conf& config;
// ev@1fce4c5e-0ab8-41bb-90f7-14277703d2ac:v1
// insert your public definitions here
ev_API::Mqtt::ValidatingMqttProxy mqtt_v{mqtt};
ev_API::ApiHelper helper{info, mqtt_v, {{"error_history_consumer", 1}}, get_config_service_client()};
// 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
auto forward_api_var(std::string const& var);
void generate_api_cmd_get_errors();
void generate_api_cmd_active_errors();
void generate_api_var_error_events();
ev_API::CommCheckHandler<generic_errorImplBase> comm_check{"generic/CommunicationFault",
ev_API::bridge_connection_lost_message, p_main};
// 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 // ERROR_HISTORY_CONSUMER_API_HPP

View File

@@ -0,0 +1,15 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
#pragma once
#include <generated/types/error_history.hpp>
#include <utils/error.hpp>
namespace error_converter {
types::error_history::ErrorObject framework_to_internal_api(Everest::error::Error const& val);
types::error_history::Severity framework_to_internal_api(Everest::error::Severity const& val);
types::error_history::ImplementationIdentifier framework_to_internal_api(ImplementationIdentifier const& val);
types::error_history::State framework_to_internal_api(Everest::error::State const& val);
} // namespace error_converter

View File

@@ -0,0 +1,16 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
#include "generic_errorImpl.hpp"
namespace module {
namespace main {
void generic_errorImpl::init() {
}
void generic_errorImpl::ready() {
}
} // namespace main
} // namespace module

View File

@@ -0,0 +1,61 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
#ifndef MAIN_GENERIC_ERROR_IMPL_HPP
#define MAIN_GENERIC_ERROR_IMPL_HPP
//
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
// template version 3
//
#include <generated/interfaces/generic_error/Implementation.hpp>
#include "../error_history_consumer_API.hpp"
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
// insert your custom include headers here
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
namespace module {
namespace main {
struct Conf {};
class generic_errorImpl : public generic_errorImplBase {
public:
generic_errorImpl() = delete;
generic_errorImpl(Everest::ModuleAdapter* ev, const Everest::PtrContainer<error_history_consumer_API>& mod,
Conf& config) :
generic_errorImplBase(ev, "main"), mod(mod), config(config){};
// ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1
// insert your public definitions here
// ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1
protected:
// no commands defined for this interface
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
// insert your protected definitions here
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
private:
const Everest::PtrContainer<error_history_consumer_API>& mod;
const Conf& config;
virtual void init() override;
virtual void ready() override;
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
// insert your private definitions here
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
};
// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1
// insert other definitions here
// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1
} // namespace main
} // namespace module
#endif // MAIN_GENERIC_ERROR_IMPL_HPP

View File

@@ -0,0 +1,26 @@
description: API for accessing the error history (consumer API)
config:
cfg_communication_check_to_s:
description: "Maximum time between two communication check events. Values <= 0 disables communication checks."
type: integer
default: 5
cfg_heartbeat_interval_ms:
description: "Interval between two heartbeat messages send by the API. Values <= 0 disable heartbeat."
type: integer
default: 1000
provides:
main:
interface: generic_error
description: "Provides errors types for module communication status."
requires:
error_history:
interface: error_history
enable_external_mqtt: true
enable_global_errors: true
metadata:
license: https://opensource.org/licenses/Apache-2.0
authors:
- Jan Christoph Habig
- Florin Mihut

View File

@@ -0,0 +1,60 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
#include "error_wrapper.hpp"
#include <utils/date.hpp>
namespace error_converter {
types::error_history::Severity framework_to_internal_api(Everest::error::Severity const& val) {
using SrcT = Everest::error::Severity;
using TarT = types::error_history::Severity;
switch (val) {
case SrcT::High:
return TarT::High;
case SrcT::Medium:
return TarT::Medium;
case SrcT::Low:
return TarT::Low;
}
throw std::out_of_range("Unexpected value for Everest::error::Severity");
}
types::error_history::ImplementationIdentifier framework_to_internal_api(ImplementationIdentifier const& val) {
types::error_history::ImplementationIdentifier result;
result.implementation_id = val.implementation_id;
result.module_id = val.module_id;
return result;
}
types::error_history::State framework_to_internal_api(Everest::error::State const& val) {
using SrcT = Everest::error::State;
using TarT = types::error_history::State;
switch (val) {
case SrcT::Active:
return TarT::Active;
case SrcT::ClearedByModule:
return TarT::ClearedByModule;
case SrcT::ClearedByReboot:
return TarT::ClearedByReboot;
}
throw std::out_of_range("Unexpected value for everest::lib::API::V1_0::types::error_history::State_External");
}
types::error_history::ErrorObject framework_to_internal_api(Everest::error::Error const& val) {
types::error_history::ErrorObject result;
result.type = val.type;
result.description = val.description;
result.message = val.message;
result.severity = framework_to_internal_api(val.severity);
result.origin = framework_to_internal_api(val.origin);
result.timestamp = Everest::Date::to_rfc3339(val.timestamp);
result.uuid = val.uuid.to_string();
result.state = framework_to_internal_api(val.state);
if (not val.sub_type.empty()) {
result.sub_type.emplace(val.sub_type);
}
return result;
}
} // namespace error_converter

View File

@@ -0,0 +1,31 @@
#
# 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_compile_options(${MODULE_NAME}
PUBLIC -Wall -Wextra -pedantic -Werror=switch)
target_link_libraries(${MODULE_NAME}
PRIVATE
atomic
everest::everest_api_types
everest::everest_api_module_helpers
)
# ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1
target_sources(${MODULE_NAME}
PRIVATE
"main/ev_board_supportImpl.cpp"
)
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1
# insert other things like install cmds etc here
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1

View File

@@ -0,0 +1,12 @@
.. _everest_modules_handwritten_ev_board_support_API:
.. *******************************************
.. ev_board_support_API
.. *******************************************
The complete API specification can be found in the
``docs/source/reference/EVerest_API/ev_board_support_API.yaml``
file in the source repository, or in the `AsyncAPI HTML documentation <../../../api/ev_board_support_API/index.html>`_ automatically generated from it.

View File

@@ -0,0 +1,119 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#include "ev_board_support_API.hpp"
#include <everest_api_types/ev_board_support/codec.hpp>
#include <everest_api_types/ev_board_support/wrapper.hpp>
#include <everest_api_types/evse_board_support/codec.hpp>
#include <everest_api_types/evse_board_support/wrapper.hpp>
#include <everest_api_types/evse_manager/codec.hpp>
#include <everest_api_types/evse_manager/wrapper.hpp>
#include <everest_api_types/generic/codec.hpp>
#include <everest_api_types/generic/string.hpp>
#include <everest_api_types/utilities/codec.hpp>
#include "everest_api_types/ev_board_support/API.hpp"
#include "utils/error.hpp"
namespace module {
namespace API_generic = API_types::generic;
using ev_API::deserialize;
void ev_board_support_API::init() {
invoke_init(*p_main);
API_types_entry::CommunicationParameters comm_params{};
comm_params.heartbeat_period_ms = config.cfg_heartbeat_interval_ms;
comm_params.communication_check_period_s = config.cfg_communication_check_to_s;
helper.init(comm_params);
}
void ev_board_support_API::ready() {
invoke_ready(*p_main);
generate_api_var_bsp_event();
generate_api_var_bsp_measurement();
generate_api_var_ev_info();
generate_api_var_raise_error();
generate_api_var_clear_error();
helper.generate_api_var_communication_check(&comm_check);
comm_check.start(config.cfg_communication_check_to_s);
helper.setup_heartbeat_generator(&comm_check, config.cfg_heartbeat_interval_ms);
helper.publish_ready_beacon();
}
void ev_board_support_API::generate_api_var_bsp_event() {
helper.subscribe_api_topic("bsp_event", [=](std::string const& data) {
API_types::evse_board_support::BspEvent ext;
if (deserialize(data, ext)) {
p_main->publish_bsp_event(to_internal_api(ext));
return true;
}
return false;
});
}
void ev_board_support_API::generate_api_var_bsp_measurement() {
helper.subscribe_api_topic("bsp_measurement", [=](std::string const& data) {
API_types::ev_board_support::BspMeasurement ext;
if (deserialize(data, ext)) {
p_main->publish_bsp_measurement(to_internal_api(ext));
return true;
}
return false;
});
}
void ev_board_support_API::generate_api_var_ev_info() {
helper.subscribe_api_topic("ev_info", [=](std::string const& data) {
API_types::evse_manager::EVInfo ext;
if (deserialize(data, ext)) {
p_main->publish_ev_info(to_internal_api(ext));
return true;
}
return false;
});
}
void ev_board_support_API::generate_api_var_raise_error() {
helper.subscribe_api_topic("raise_error", [=](std::string const& data) {
API_types::generic::Error error;
if (deserialize(data, error)) {
auto sub_type_str = error.sub_type ? error.sub_type.value() : "";
auto message_str = error.message ? error.message.value() : "";
auto error_str = make_error_string(error);
auto ev_error = p_main->error_factory->create_error(error_str, sub_type_str, message_str,
Everest::error::Severity::High);
p_main->raise_error(ev_error);
return true;
}
return false;
});
}
void ev_board_support_API::generate_api_var_clear_error() {
helper.subscribe_api_topic("clear_error", [=](std::string const& data) {
API_types::generic::Error error;
if (deserialize(data, error)) {
std::string error_str = make_error_string(error);
if (error.sub_type) {
p_main->clear_error(error_str, error.sub_type.value());
} else {
p_main->clear_error(error_str);
}
return true;
}
return false;
});
}
std::string ev_board_support_API::make_error_string(API_types::generic::Error const& error) {
auto error_str = API_generic::trimmed(serialize(error.type));
auto result = "generic/" + error_str;
return result;
}
} // namespace module

View File

@@ -0,0 +1,92 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#ifndef EV_BOARD_SUPPORT_API_HPP
#define EV_BOARD_SUPPORT_API_HPP
//
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
// template version 2
//
#include "ld-ev.hpp"
// headers for provided interface implementations
#include <generated/interfaces/ev_board_support/Implementation.hpp>
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
// insert your custom include headers here
#include <everest_api_module_helpers/ApiHelper.hpp>
#include <everest_api_types/entrypoint/API.hpp>
#include <everest_api_types/ev_board_support/API.hpp>
#include <everest_api_types/evse_board_support/API.hpp>
#include <everest_api_types/generic/API.hpp>
namespace ev_API = everest::lib::API;
namespace ev_API_v = everest::lib::API::V1_0;
namespace API_types = ev_API::V1_0::types;
namespace API_types_entry = API_types::entrypoint;
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
namespace module {
struct Conf {
int cfg_communication_check_to_s;
int cfg_heartbeat_interval_ms;
};
class ev_board_support_API : public Everest::ModuleBase {
public:
ev_board_support_API() = delete;
ev_board_support_API(const ModuleInfo& info, Everest::MqttProvider& mqtt_provider,
std::unique_ptr<ev_board_supportImplBase> p_main, Conf& config) :
ModuleBase(info), mqtt(mqtt_provider), p_main(std::move(p_main)), config(config){};
Everest::MqttProvider& mqtt;
const std::unique_ptr<ev_board_supportImplBase> p_main;
const Conf& config;
// ev@1fce4c5e-0ab8-41bb-90f7-14277703d2ac:v1
// insert your public definitions here
ev_API::Mqtt::ValidatingMqttProxy mqtt_v{mqtt};
ev_API::ApiHelper helper{info, mqtt_v, {{"ev_board_support", 1}}, get_config_service_client()};
// 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
using HandleErrorFtor = std::function<void()>;
struct ErrorHandler {
HandleErrorFtor raiser;
HandleErrorFtor clearer;
std::string error_id;
};
void generate_api_var_bsp_event();
void generate_api_var_bsp_measurement();
void generate_api_var_ev_info();
void generate_api_var_raise_error();
void generate_api_var_clear_error();
std::string make_error_string(ev_API_v::types::generic::Error const& error);
ev_API::CommCheckHandler<ev_board_supportImplBase> comm_check{"generic/CommunicationFault",
ev_API::bridge_connection_lost_message, p_main};
// 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 // EV_BOARD_SUPPORT_API_HPP

View File

@@ -0,0 +1,77 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#include "ev_board_supportImpl.hpp"
#include <everest/logging.hpp>
#include <everest_api_types/ev_board_support/API.hpp>
#include <everest_api_types/ev_board_support/codec.hpp>
#include <everest_api_types/ev_board_support/json_codec.hpp>
#include <everest_api_types/ev_board_support/wrapper.hpp>
#include <everest_api_types/evse_board_support/API.hpp>
#include <everest_api_types/evse_board_support/codec.hpp>
#include <everest_api_types/evse_board_support/json_codec.hpp>
#include <everest_api_types/evse_board_support/wrapper.hpp>
#include <everest_api_types/generic/codec.hpp>
#include <generated/types/board_support_common.hpp>
#include <generated/types/evse_board_support.hpp>
using namespace everest::lib::API;
namespace generic = everest::lib::API::V1_0::types::generic;
namespace API_types = ev_API::V1_0::types;
namespace module {
namespace main {
void ev_board_supportImpl::init() {
}
void ev_board_supportImpl::ready() {
}
void ev_board_supportImpl::handle_enable(bool& value) {
static const auto topic = mod->helper.get_topics().everest_to_extern("enable");
const auto data = generic::serialize(value);
mod->mqtt_v.publish(topic, data);
}
void ev_board_supportImpl::handle_set_cp_state(types::ev_board_support::EvCpState& cp_state) {
static const auto topic = mod->helper.get_topics().everest_to_extern("set_cp_state");
const auto ext = API_types::ev_board_support::to_external_api(cp_state);
const auto data = serialize(ext);
mod->mqtt_v.publish(topic, data);
}
void ev_board_supportImpl::handle_allow_power_on(bool& value) {
static const auto topic = mod->helper.get_topics().everest_to_extern("allow_power_on");
const auto data = generic::serialize(value);
mod->mqtt_v.publish(topic, data);
}
void ev_board_supportImpl::handle_diode_fail(bool& value) {
static const auto topic = mod->helper.get_topics().everest_to_extern("diode_fail");
const auto data = generic::serialize(value);
mod->mqtt_v.publish(topic, data);
}
void ev_board_supportImpl::handle_set_ac_max_current(double& current) {
static const auto topic = mod->helper.get_topics().everest_to_extern("set_ac_max_current");
const auto data = generic::serialize(current);
mod->mqtt_v.publish(topic, data);
}
void ev_board_supportImpl::handle_set_three_phases(bool& three_phases) {
static const auto topic = mod->helper.get_topics().everest_to_extern("set_three_phases");
const auto data = generic::serialize(three_phases);
mod->mqtt_v.publish(topic, data);
}
void ev_board_supportImpl::handle_set_rcd_error(double& rcd_current_mA) {
static const auto topic = mod->helper.get_topics().everest_to_extern("set_rcd_error");
const auto data = generic::serialize(rcd_current_mA);
mod->mqtt_v.publish(topic, data);
}
} // namespace main
} // namespace module

View File

@@ -0,0 +1,68 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#ifndef MAIN_EV_BOARD_SUPPORT_IMPL_HPP
#define MAIN_EV_BOARD_SUPPORT_IMPL_HPP
//
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
// template version 3
//
#include <generated/interfaces/ev_board_support/Implementation.hpp>
#include "../ev_board_support_API.hpp"
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
// insert your custom include headers here
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
namespace module {
namespace main {
struct Conf {};
class ev_board_supportImpl : public ev_board_supportImplBase {
public:
ev_board_supportImpl() = delete;
ev_board_supportImpl(Everest::ModuleAdapter* ev, const Everest::PtrContainer<ev_board_support_API>& mod,
Conf& config) :
ev_board_supportImplBase(ev, "main"), mod(mod), config(config){};
// ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1
// insert your public definitions here
// ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1
protected:
// command handler functions (virtual)
virtual void handle_enable(bool& value) override;
virtual void handle_set_cp_state(types::ev_board_support::EvCpState& cp_state) override;
virtual void handle_allow_power_on(bool& value) override;
virtual void handle_diode_fail(bool& value) override;
virtual void handle_set_ac_max_current(double& current) override;
virtual void handle_set_three_phases(bool& three_phases) override;
virtual void handle_set_rcd_error(double& rcd_current_mA) override;
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
// insert your protected definitions here
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
private:
const Everest::PtrContainer<ev_board_support_API>& mod;
const Conf& config;
virtual void init() override;
virtual void ready() override;
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
// insert your private definitions here
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
};
// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1
// insert other definitions here
// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1
} // namespace main
} // namespace module
#endif // MAIN_EV_BOARD_SUPPORT_IMPL_HPP

View File

@@ -0,0 +1,21 @@
description: API for EV board support
config:
cfg_communication_check_to_s:
description: "Maximum time between two communication check events. Values <= 0 disables communication checks."
type: integer
default: 5
cfg_heartbeat_interval_ms:
description: "Interval between two heartbeat messages send by the API. Values <= 0 disable heartbeat"
type: integer
default: 1000
provides:
main:
interface: ev_board_support
description: "Allows EVerest to control EV boards via API clients."
enable_external_mqtt: true
metadata:
license: https://opensource.org/licenses/Apache-2.0
authors:
- Jan Christoph Habig

View File

@@ -0,0 +1,33 @@
#
# 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_compile_options(${MODULE_NAME}
PUBLIC -Wall -Wextra -pedantic -Werror=switch)
target_link_libraries(${MODULE_NAME}
PRIVATE
atomic
everest::everest_api_types
everest::everest_api_module_helpers
)
# ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1
target_sources(${MODULE_NAME}
PRIVATE
"main/evse_board_supportImpl.cpp"
"rcd/ac_rcdImpl.cpp"
"connector_lock/connector_lockImpl.cpp"
)
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1
# insert other things like install cmds etc here
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1

View File

@@ -0,0 +1,28 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
#include "connector_lockImpl.hpp"
namespace module {
namespace connector_lock {
using namespace std::literals::chrono_literals;
void connector_lockImpl::init() {
}
void connector_lockImpl::ready() {
}
void connector_lockImpl::handle_lock() {
static const auto topic = mod->helper.get_topics().everest_to_extern("lock");
mod->mqtt_v.publish(topic, "");
}
void connector_lockImpl::handle_unlock() {
static const auto topic = mod->helper.get_topics().everest_to_extern("unlock");
mod->mqtt_v.publish(topic, "");
}
} // namespace connector_lock
} // namespace module

View File

@@ -0,0 +1,63 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
#ifndef CONNECTOR_LOCK_CONNECTOR_LOCK_IMPL_HPP
#define CONNECTOR_LOCK_CONNECTOR_LOCK_IMPL_HPP
//
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
// template version 3
//
#include <generated/interfaces/connector_lock/Implementation.hpp>
#include "../evse_board_support_API.hpp"
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
// insert your custom include headers here
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
namespace module {
namespace connector_lock {
struct Conf {};
class connector_lockImpl : public connector_lockImplBase {
public:
connector_lockImpl() = delete;
connector_lockImpl(Everest::ModuleAdapter* ev, const Everest::PtrContainer<evse_board_support_API>& mod,
Conf& config) :
connector_lockImplBase(ev, "connector_lock"), mod(mod), config(config){};
// ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1
// insert your public definitions here
// ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1
protected:
// command handler functions (virtual)
virtual void handle_lock() override;
virtual void handle_unlock() override;
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
// insert your protected definitions here
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
private:
const Everest::PtrContainer<evse_board_support_API>& mod;
const Conf& config;
virtual void init() override;
virtual void ready() override;
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
// insert your private definitions here
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
};
// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1
// insert other definitions here
// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1
} // namespace connector_lock
} // namespace module
#endif // CONNECTOR_LOCK_CONNECTOR_LOCK_IMPL_HPP

View File

@@ -0,0 +1,11 @@
.. _everest_modules_handwritten_evse_board_support_API:
.. *******************************************
.. evse_board_support_API
.. *******************************************
The complete API specification can be found in the
``docs/source/reference/EVerest_API/evse_board_support_API.yaml``
file in the source repository, or in the `AsyncAPI HTML documentation <../../../../api/evse_board_support_API/index.html>`_ automatically generated from it.

View File

@@ -0,0 +1,221 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
#include "evse_board_support_API.hpp"
#include <everest_api_types/evse_board_support/API.hpp>
#include <everest_api_types/evse_board_support/codec.hpp>
#include <everest_api_types/evse_board_support/wrapper.hpp>
#include <everest_api_types/evse_manager/API.hpp>
#include <everest_api_types/evse_manager/codec.hpp>
#include <everest_api_types/evse_manager/wrapper.hpp>
#include <everest_api_types/generic/codec.hpp>
#include <everest_api_types/generic/string.hpp>
#include <everest_api_types/utilities/codec.hpp>
#include "utils/error.hpp"
namespace module {
namespace API_evse_manager = API_types::evse_manager;
namespace API_generic = API_types::generic;
using ev_API::deserialize;
void evse_board_support_API::init() {
invoke_init(*p_main);
invoke_init(*p_rcd);
invoke_init(*p_connector_lock);
API_types_entry::CommunicationParameters comm_params{};
comm_params.heartbeat_period_ms = config.cfg_heartbeat_interval_ms;
comm_params.communication_check_period_s = config.cfg_communication_check_to_s;
comm_params.request_reply_timeout_s = config.cfg_request_reply_to_s;
helper.init(comm_params);
}
void evse_board_support_API::ready() {
invoke_ready(*p_main);
invoke_ready(*p_rcd);
invoke_ready(*p_connector_lock);
generate_api_var_event();
generate_api_var_ac_nr_of_phases();
generate_api_var_capabilities();
generate_api_var_ac_pp_ampacity();
generate_api_var_request_stop_transaction();
generate_api_var_rcd_current();
generate_api_var_raise_error();
generate_api_var_clear_error();
helper.generate_api_var_communication_check(&comm_check);
comm_check.start(config.cfg_communication_check_to_s);
helper.setup_heartbeat_generator(&comm_check, config.cfg_heartbeat_interval_ms);
helper.publish_ready_beacon();
}
void evse_board_support_API::generate_api_var_event() {
helper.subscribe_api_topic("event", [=](std::string const& data) {
API_types_ext::BspEvent ext;
if (deserialize(data, ext)) {
p_main->publish_event(to_internal_api(ext));
return true;
}
return false;
});
}
void evse_board_support_API::generate_api_var_ac_nr_of_phases() {
helper.subscribe_api_topic("ac_nr_of_phases", [=](std::string const& data) {
int ac_nr_of_phases_available = 0;
if (deserialize(data, ac_nr_of_phases_available)) {
p_main->publish_ac_nr_of_phases_available(ac_nr_of_phases_available);
return true;
}
return false;
});
}
void evse_board_support_API::generate_api_var_capabilities() {
helper.subscribe_api_topic("capabilities", [=](std::string const& data) {
API_types_ext::HardwareCapabilities ext;
if (deserialize(data, ext)) {
p_main->publish_capabilities(to_internal_api(ext));
return true;
}
return false;
});
}
void evse_board_support_API::generate_api_var_ac_pp_ampacity() {
helper.subscribe_api_topic("ac_pp_ampacity", [=](std::string const& data) {
API_types_ext::ProximityPilot ext;
if (deserialize(data, ext)) {
p_main->publish_ac_pp_ampacity(to_internal_api(ext));
return true;
}
return false;
});
}
void evse_board_support_API::generate_api_var_request_stop_transaction() {
helper.subscribe_api_topic("request_stop_transaction", [=](std::string const& data) {
API_evse_manager::StopTransactionRequest ext;
if (deserialize(data, ext)) {
p_main->publish_request_stop_transaction(to_internal_api(ext));
return true;
}
return false;
});
}
void evse_board_support_API::generate_api_var_rcd_current() {
helper.subscribe_api_topic("rcd_current", [=](std::string const& data) {
double rcd_current;
if (deserialize(data, rcd_current)) {
p_rcd->publish_rcd_current_mA(rcd_current);
return true;
}
return false;
});
}
void evse_board_support_API::generate_api_var_raise_error() {
helper.subscribe_api_topic("raise_error", [=](std::string const& data) {
API_types_ext::Error error;
if (deserialize(data, error)) {
auto handler = make_error_handler(error);
handler.raiser();
return true;
}
return false;
});
}
void evse_board_support_API::generate_api_var_clear_error() {
helper.subscribe_api_topic("clear_error", [=](std::string const& data) {
API_types_ext::Error error;
if (deserialize(data, error)) {
auto handler = make_error_handler(error);
handler.clearer();
return true;
}
return false;
});
}
evse_board_support_API::ErrorHandler evse_board_support_API::make_error_handler(API_types_ext::Error const& error) {
using namespace API_types_ext;
auto error_str = API_generic::trimmed(serialize(error.type));
ErrorHandler result;
auto sub_type_str = error.sub_type ? error.sub_type.value() : "";
auto message_str = error.message ? error.message.value() : "";
std::string error_id;
switch (error.type) {
case ErrorEnum::DiodeFault:
case ErrorEnum::VentilationNotAvailable:
case ErrorEnum::BrownOut:
case ErrorEnum::EnergyManagement:
case ErrorEnum::PermanentFault:
case ErrorEnum::MREC2GroundFailure:
case ErrorEnum::MREC3HighTemperature:
case ErrorEnum::MREC4OverCurrentFailure:
case ErrorEnum::MREC5OverVoltage:
case ErrorEnum::MREC6UnderVoltage:
case ErrorEnum::MREC8EmergencyStop:
case ErrorEnum::MREC10InvalidVehicleMode:
case ErrorEnum::MREC14PilotFault:
case ErrorEnum::MREC15PowerLoss:
case ErrorEnum::MREC17EVSEContactorFault:
case ErrorEnum::MREC18CableOverTempDerate:
case ErrorEnum::MREC19CableOverTempStop:
case ErrorEnum::MREC20PartialInsertion:
case ErrorEnum::MREC23ProximityFault:
case ErrorEnum::MREC24ConnectorVoltageHigh:
case ErrorEnum::MREC25BrokenLatch:
case ErrorEnum::MREC26CutCable:
case ErrorEnum::TiltDetected:
case ErrorEnum::WaterIngressDetected:
case ErrorEnum::EnclosureOpen:
case ErrorEnum::VendorError:
case ErrorEnum::VendorWarning:
case ErrorEnum::CommunicationFault:
error_id = "evse_board_support/" + error_str;
result.raiser = [this, sub_type_str, message_str, error_id]() {
auto ev_error = p_main->error_factory->create_error(error_id, sub_type_str, message_str,
Everest::error::Severity::High);
p_main->raise_error(ev_error);
};
result.clearer = [this, error_id, sub_type_str] { p_main->clear_error(error_id, sub_type_str); };
break;
case ErrorEnum::ConnectorLockCapNotCharged:
case ErrorEnum::ConnectorLockUnexpectedOpen:
case ErrorEnum::ConnectorLockUnexpectedClose:
case ErrorEnum::ConnectorLockFailedLock:
case ErrorEnum::ConnectorLockFailedUnlock:
case ErrorEnum::MREC1ConnectorLockFailure:
error_id = "connector_lock/" + error_str;
result.raiser = [this, sub_type_str, message_str, error_id]() {
auto ev_error = p_connector_lock->error_factory->create_error(error_id, sub_type_str, message_str,
Everest::error::Severity::High);
p_connector_lock->raise_error(ev_error);
};
result.clearer = [this, error_id, sub_type_str] { p_connector_lock->clear_error(error_id, sub_type_str); };
break;
case ErrorEnum::Selftest:
case ErrorEnum::DC:
case ErrorEnum::AC:
error_id = "acd_rcd/" + error_str;
result.raiser = [this, sub_type_str, message_str, error_id]() {
auto ev_error =
p_rcd->error_factory->create_error(error_id, sub_type_str, message_str, Everest::error::Severity::High);
p_rcd->raise_error(ev_error);
};
result.clearer = [this, error_id, sub_type_str] { p_rcd->clear_error(error_id, sub_type_str); };
break;
}
return result;
}
} // namespace module

View File

@@ -0,0 +1,105 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
#ifndef EVSE_BOARD_SUPPORT_API_HPP
#define EVSE_BOARD_SUPPORT_API_HPP
//
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
// template version 2
//
#include "ld-ev.hpp"
// headers for provided interface implementations
#include <generated/interfaces/ac_rcd/Implementation.hpp>
#include <generated/interfaces/connector_lock/Implementation.hpp>
#include <generated/interfaces/evse_board_support/Implementation.hpp>
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
// insert your custom include headers here
#include <everest_api_module_helpers/ApiHelper.hpp>
#include <everest_api_types/entrypoint/API.hpp>
#include <everest_api_types/evse_board_support/API.hpp>
namespace ev_API = everest::lib::API;
namespace ev_API_v = everest::lib::API::V1_0;
namespace API_types = ev_API::V1_0::types;
namespace API_types_entry = API_types::entrypoint;
namespace API_types_ext = ev_API_v::types::evse_board_support;
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
namespace module {
struct Conf {
int cfg_communication_check_to_s;
int cfg_heartbeat_interval_ms;
int cfg_request_reply_to_s;
};
class evse_board_support_API : public Everest::ModuleBase {
public:
evse_board_support_API() = delete;
evse_board_support_API(const ModuleInfo& info, Everest::MqttProvider& mqtt_provider,
std::unique_ptr<evse_board_supportImplBase> p_main, std::unique_ptr<ac_rcdImplBase> p_rcd,
std::unique_ptr<connector_lockImplBase> p_connector_lock, Conf& config) :
ModuleBase(info),
mqtt(mqtt_provider),
p_main(std::move(p_main)),
p_rcd(std::move(p_rcd)),
p_connector_lock(std::move(p_connector_lock)),
config(config){};
Everest::MqttProvider& mqtt;
const std::unique_ptr<evse_board_supportImplBase> p_main;
const std::unique_ptr<ac_rcdImplBase> p_rcd;
const std::unique_ptr<connector_lockImplBase> p_connector_lock;
const Conf& config;
// ev@1fce4c5e-0ab8-41bb-90f7-14277703d2ac:v1
// insert your public definitions here
ev_API::Mqtt::ValidatingMqttProxy mqtt_v{mqtt};
ev_API::ApiHelper helper{info, mqtt_v, {{"evse_board_support", 1}}, get_config_service_client()};
// 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
using HandleErrorFtor = std::function<void()>;
struct ErrorHandler {
HandleErrorFtor raiser;
HandleErrorFtor clearer;
std::string error_id;
};
void generate_api_var_event();
void generate_api_var_ac_nr_of_phases();
void generate_api_var_capabilities();
void generate_api_var_ac_pp_ampacity();
void generate_api_var_request_stop_transaction();
void generate_api_var_rcd_current();
void generate_api_var_raise_error();
void generate_api_var_clear_error();
ErrorHandler make_error_handler(API_types_ext::Error const& error);
ev_API::CommCheckHandler<evse_board_supportImplBase> comm_check{"evse_board_support/CommunicationFault",
ev_API::bridge_connection_lost_message, p_main};
// 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 // EVSE_BOARD_SUPPORT_API_HPP

View File

@@ -0,0 +1,78 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
#include "evse_board_supportImpl.hpp"
#include <everest/logging.hpp>
#include <everest_api_types/evse_board_support/API.hpp>
#include <everest_api_types/evse_board_support/codec.hpp>
#include <everest_api_types/evse_board_support/json_codec.hpp>
#include <everest_api_types/evse_board_support/wrapper.hpp>
#include <everest_api_types/generic/codec.hpp>
#include <everest_api_types/utilities/AsyncApiRequestReply.hpp>
#include <generated/types/board_support_common.hpp>
#include <generated/types/evse_board_support.hpp>
namespace module {
namespace main {
using namespace everest::lib::API;
namespace generic = everest::lib::API::V1_0::types::generic;
void evse_board_supportImpl::init() {
timeout_s = mod->config.cfg_request_reply_to_s;
}
void evse_board_supportImpl::ready() {
}
void evse_board_supportImpl::handle_enable(bool& value) {
static const auto topic = mod->helper.get_topics().everest_to_extern("enable");
auto data = generic::serialize(value);
mod->mqtt_v.publish(topic, data);
}
void evse_board_supportImpl::handle_pwm_on(double& value) {
static const auto topic = mod->helper.get_topics().everest_to_extern("pwm_on");
auto data = generic::serialize(value);
mod->mqtt_v.publish(topic, data);
}
void evse_board_supportImpl::handle_cp_state_X1() {
static const auto topic = mod->helper.get_topics().everest_to_extern("cp_state_X1");
mod->mqtt_v.publish(topic, "");
}
void evse_board_supportImpl::handle_cp_state_F() {
static const auto topic = mod->helper.get_topics().everest_to_extern("cp_state_F");
mod->mqtt_v.publish(topic, "");
}
void evse_board_supportImpl::handle_cp_state_E() {
static const auto topic = mod->helper.get_topics().everest_to_extern("cp_state_E");
mod->mqtt_v.publish(topic, "");
}
void evse_board_supportImpl::handle_allow_power_on(types::evse_board_support::PowerOnOff& value) {
static const auto topic = mod->helper.get_topics().everest_to_extern("allow_power_on");
auto ext = API_types_ext::to_external_api(value);
auto data = API_types_ext::serialize(ext);
mod->mqtt_v.publish(topic, data);
}
void evse_board_supportImpl::handle_ac_switch_three_phases_while_charging(bool& value) {
static const auto topic = mod->helper.get_topics().everest_to_extern("ac_switch_three_phases_while_charging");
std::string raw_data = value ? "ThreePhases" : "SinglePhase";
auto data = generic::serialize(raw_data);
mod->mqtt_v.publish(topic, data);
}
void evse_board_supportImpl::handle_ac_set_overcurrent_limit_A(double& value) {
static const auto topic = mod->helper.get_topics().everest_to_extern("ac_overcurrent_limit");
auto data = generic::serialize(value);
mod->mqtt_v.publish(topic, data);
}
} // namespace main
} // namespace module

View File

@@ -0,0 +1,75 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
#ifndef MAIN_EVSE_BOARD_SUPPORT_IMPL_HPP
#define MAIN_EVSE_BOARD_SUPPORT_IMPL_HPP
//
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
// template version 3
//
#include <generated/interfaces/evse_board_support/Implementation.hpp>
#include "../evse_board_support_API.hpp"
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
// insert your custom include headers here
#include <everest_api_types/evse_board_support/API.hpp>
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
namespace module {
namespace main {
struct Conf {};
class evse_board_supportImpl : public evse_board_supportImplBase {
public:
evse_board_supportImpl() = delete;
evse_board_supportImpl(Everest::ModuleAdapter* ev, const Everest::PtrContainer<evse_board_support_API>& mod,
Conf& config) :
evse_board_supportImplBase(ev, "main"), mod(mod), config(config){};
// ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1
// insert your public definitions here
// ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1
protected:
// command handler functions (virtual)
virtual void handle_enable(bool& value) override;
virtual void handle_pwm_on(double& value) override;
virtual void handle_cp_state_X1() override;
virtual void handle_cp_state_F() override;
virtual void handle_cp_state_E() override;
virtual void handle_allow_power_on(types::evse_board_support::PowerOnOff& value) override;
virtual void handle_ac_switch_three_phases_while_charging(bool& value) override;
virtual void handle_ac_set_overcurrent_limit_A(double& value) override;
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
// insert your protected definitions here
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
private:
const Everest::PtrContainer<evse_board_support_API>& mod;
const Conf& config;
virtual void init() override;
virtual void ready() override;
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
// insert your private definitions here
template <class T, class ReqT>
auto generic_request_reply(T const& default_value, ReqT const& request, std::string const& topic);
int timeout_s{5};
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
};
// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1
// insert other definitions here
// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1
} // namespace main
} // namespace module
#endif // MAIN_EVSE_BOARD_SUPPORT_IMPL_HPP

View File

@@ -0,0 +1,37 @@
description: API for EVSE board support
config:
cfg_communication_check_to_s:
description: "Maximum time between two communication check events. Values <= 0 disables communication checks."
type: integer
default: 5
cfg_heartbeat_interval_ms:
description: "Interval between two heartbeat messages send by the API. Values <= 0 disable heartbeat"
type: integer
default: 1000
cfg_request_reply_to_s:
description: "Maximum time between request and reply. After timeout the request is answered with a default response."
type: integer
default: 550
minimum: 1
maximum: 550
provides:
main:
interface: evse_board_support
description: "Allows EVerest to control EVSE boards via API clients."
rcd:
interface: ac_rcd
description: "Allows EVerest to control/monitor AC RCD devices via API clients."
connector_lock:
interface: connector_lock
description: "Allows EVerest to control the connector/motor lock via API clients."
enable_external_mqtt: true
metadata:
license: https://opensource.org/licenses/Apache-2.0
authors:
- James Chapman
- Cornelius Claussen
- Jan Christoph Habig
- Andreas Heinrich
- Florin Mihut

View File

@@ -0,0 +1,48 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
#include "ac_rcdImpl.hpp"
#include <everest_api_types/utilities/AsyncApiRequestReply.hpp>
namespace {
bool to_external_api(bool value) {
return value;
}
} // namespace
namespace module {
namespace rcd {
using namespace everest::lib::API;
void ac_rcdImpl::init() {
timeout_s = mod->config.cfg_request_reply_to_s;
}
void ac_rcdImpl::ready() {
}
template <class T, class ReqT>
auto ac_rcdImpl::generic_request_reply(T const& default_value, ReqT const& request, std::string const& topic) {
using namespace API_types_ext;
using ExtT = decltype(to_external_api(std::declval<T>()));
auto result = request_reply_handler<ExtT>(mod->mqtt_v, mod->helper.get_topics(), request, topic, timeout_s);
if (!result) {
return default_value;
}
return result.value();
}
void ac_rcdImpl::handle_self_test() {
static const auto topic = mod->helper.get_topics().everest_to_extern("self_test");
mod->mqtt_v.publish(topic, "");
}
bool ac_rcdImpl::handle_reset() {
static bool default_response = false;
return generic_request_reply(default_response, internal::empty_payload, "reset");
}
} // namespace rcd
} // namespace module

View File

@@ -0,0 +1,65 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
#ifndef RCD_AC_RCD_IMPL_HPP
#define RCD_AC_RCD_IMPL_HPP
//
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
// template version 3
//
#include <generated/interfaces/ac_rcd/Implementation.hpp>
#include "../evse_board_support_API.hpp"
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
namespace module {
namespace rcd {
struct Conf {};
class ac_rcdImpl : public ac_rcdImplBase {
public:
ac_rcdImpl() = delete;
ac_rcdImpl(Everest::ModuleAdapter* ev, const Everest::PtrContainer<evse_board_support_API>& mod, Conf& config) :
ac_rcdImplBase(ev, "rcd"), mod(mod), config(config){};
// ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1
// insert your public definitions here
// ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1
protected:
// command handler functions (virtual)
virtual void handle_self_test() override;
virtual bool handle_reset() override;
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
// insert your protected definitions here
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
private:
const Everest::PtrContainer<evse_board_support_API>& mod;
const Conf& config;
virtual void init() override;
virtual void ready() override;
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
template <class T, class ReqT>
auto generic_request_reply(T const& default_value, ReqT const& request, std::string const& topic);
int timeout_s{5};
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
};
// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1
// insert other definitions here
// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1
} // namespace rcd
} // namespace module
#endif // RCD_AC_RCD_IMPL_HPP

View File

@@ -0,0 +1,37 @@
#
# 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_compile_options(${MODULE_NAME}
PUBLIC -Wall -Wextra -pedantic -Werror=switch)
target_link_libraries(${MODULE_NAME}
PRIVATE
atomic
everest::everest_api_types
everest::util
everest::everest_api_module_helpers
)
# ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1
target_sources(${MODULE_NAME}
PRIVATE
"main/generic_errorImpl.cpp"
)
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1
# insert other things like install cmds etc here
target_sources(${MODULE_NAME}
PRIVATE
"evse_manager_consumer_API.cpp"
"session_info.cpp"
)
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1

View File

@@ -0,0 +1,18 @@
.. _everest_modules_handwritten_evse_manager_consumer_API:
.. *******************************************
.. evse_manager_consumer_API
.. *******************************************
The complete API specification can be found in the
``docs/source/reference/EVerest_API/evse_manager_consumer_API.yaml``
file in the source repository, or in the `AsyncAPI HTML documentation <../../../../api/evse_manager_consumer_API/index.html>`_ automatically generated from it.
Session Info
=============
This API module additionally provides a ``SessionInfo`` class to represent information about EVSE sessions.
The data for ``SessionInfo`` is not simply forwarded from the internal EVerest representation. The internal
represenation is processed and converted to the external API representation.

View File

@@ -0,0 +1,366 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
#include "evse_manager_consumer_API.hpp"
#include <everest_api_types/auth/wrapper.hpp>
#include <everest_api_types/energy/API.hpp>
#include <everest_api_types/energy/codec.hpp>
#include <everest_api_types/energy/wrapper.hpp>
#include <everest_api_types/evse_board_support/API.hpp>
#include <everest_api_types/evse_board_support/codec.hpp>
#include <everest_api_types/evse_board_support/wrapper.hpp>
#include <everest_api_types/evse_manager/API.hpp>
#include <everest_api_types/evse_manager/codec.hpp>
#include <everest_api_types/evse_manager/wrapper.hpp>
#include <everest_api_types/generic/codec.hpp>
#include <everest_api_types/generic/string.hpp>
#include <everest_api_types/iso15118_charger/API.hpp>
#include <everest_api_types/iso15118_charger/codec.hpp>
#include <everest_api_types/iso15118_charger/wrapper.hpp>
#include <everest_api_types/isolation_monitor/API.hpp>
#include <everest_api_types/isolation_monitor/codec.hpp>
#include <everest_api_types/isolation_monitor/wrapper.hpp>
#include <everest_api_types/power_supply_DC/API.hpp>
#include <everest_api_types/power_supply_DC/codec.hpp>
#include <everest_api_types/power_supply_DC/wrapper.hpp>
#include <everest_api_types/powermeter/codec.hpp>
#include <everest_api_types/powermeter/wrapper.hpp>
#include <everest_api_types/uk_random_delay/API.hpp>
#include <everest_api_types/uk_random_delay/codec.hpp>
#include <everest_api_types/uk_random_delay/wrapper.hpp>
#include <everest_api_types/utilities/AsyncApiRequestReply.hpp>
#include <everest_api_types/utilities/codec.hpp>
#include <everest/logging.hpp>
namespace {
template <class T> T const& to_external_api(T const& val) {
return val;
}
} // namespace
namespace module {
namespace API_types_ext = API_types::evse_manager;
namespace API_powermeter = API_types::powermeter;
namespace API_iso = API_types::iso15118_charger;
namespace API_energy = API_types::energy;
namespace API_evse_bsp = API_types::evse_board_support;
namespace API_imd = API_types::isolation_monitor;
namespace API_dc = API_types::power_supply_DC;
namespace API_random_delay = API_types::uk_random_delay;
namespace API_generic = API_types::generic;
using ev_API::deserialize;
void evse_manager_consumer_API::init() {
invoke_init(*p_main);
API_types_entry::CommunicationParameters comm_params{};
comm_params.heartbeat_period_ms = config.cfg_heartbeat_interval_ms;
comm_params.communication_check_period_s = config.cfg_communication_check_to_s;
comm_params.request_reply_timeout_s = config.cfg_request_reply_to_s;
helper.init(comm_params);
}
void evse_manager_consumer_API::ready() {
invoke_ready(*p_main);
generate_api_cmd_get_evse();
generate_api_cmd_enable_disable();
generate_api_cmd_pause_charging();
generate_api_cmd_resume_charging();
generate_api_cmd_stop_transaction();
generate_api_cmd_force_unlock();
generate_api_cmd_random_delay_enable();
generate_api_cmd_random_delay_disable();
generate_api_cmd_random_delay_cancel();
generate_api_cmd_random_delay_set_duration_s();
generate_api_var_session_event();
generate_api_var_hlc_session_failed();
generate_api_var_session_info(); // special, not just forwarded
generate_api_var_ev_info();
generate_api_var_powermeter();
generate_api_var_evse_id();
generate_api_var_hw_capabilities();
generate_api_var_enforced_limits();
generate_api_var_selected_protocol();
generate_api_var_powermeter_public_key_ocmf();
generate_api_var_supported_energy_transfer_modes();
generate_api_var_ac_nr_of_phases_available();
generate_api_var_ac_pp_ampacity();
generate_api_var_dlink_ready();
generate_api_var_isolation_measurement();
generate_api_var_dc_voltage_current();
generate_api_var_dc_mode();
generate_api_var_dc_capabilities();
generate_api_var_random_delay_countdown();
helper.generate_api_var_communication_check(&comm_check);
comm_check.start(config.cfg_communication_check_to_s);
helper.setup_heartbeat_generator(&comm_check, config.cfg_heartbeat_interval_ms);
helper.publish_ready_beacon();
}
auto evse_manager_consumer_API::forward_api_var(std::string const& var) {
using namespace API_types_ext;
using namespace API_powermeter;
using namespace API_generic;
using namespace API_energy;
using namespace API_evse_bsp;
using namespace API_iso;
using namespace API_imd;
using namespace API_dc;
using namespace API_random_delay;
const auto topic = helper.get_topics().everest_to_extern(var);
return [this, topic](auto const& val) {
try {
auto&& external = to_external_api(val);
auto&& payload = serialize(external);
mqtt_v.publish(topic, payload);
} catch (const std::exception& e) {
EVLOG_warning << "Variable: '" << topic << "' failed with -> " << e.what();
} catch (...) {
EVLOG_warning << "Invalid data: Cannot convert internal to external or serialize it.\n" << topic;
}
};
}
void evse_manager_consumer_API::generate_api_cmd_get_evse() {
helper.subscribe_api_topic("get_evse", [this](std::string const& data) {
API_generic::RequestReply msg;
if (deserialize(data, msg)) {
auto reply = API_types_ext::to_external_api(r_evse_manager->call_get_evse());
mqtt_v.publish(msg.replyTo, serialize(reply));
return true;
}
return false;
});
}
void evse_manager_consumer_API::generate_api_cmd_enable_disable() {
helper.subscribe_api_topic("enable_disable", [this](std::string const& data) {
API_generic::RequestReply msg;
if (deserialize(data, msg)) {
API_types_ext::EnableDisableRequest payload;
if (deserialize(msg.payload, payload)) {
auto reply = r_evse_manager->call_enable_disable(payload.connector_id, to_internal_api(payload.source));
mqtt_v.publish(msg.replyTo, API_generic::serialize(reply));
return true;
}
}
return false;
});
}
void evse_manager_consumer_API::generate_api_cmd_pause_charging() {
helper.subscribe_api_topic("pause_charging", [this](std::string const& data) {
API_generic::RequestReply msg;
if (deserialize(data, msg)) {
auto result = r_evse_manager->call_pause_charging();
mqtt_v.publish(msg.replyTo, result);
return true;
}
return false;
});
}
void evse_manager_consumer_API::generate_api_cmd_resume_charging() {
helper.subscribe_api_topic("resume_charging", [this](std::string const& data) {
API_generic::RequestReply msg;
if (deserialize(data, msg)) {
auto result = r_evse_manager->call_resume_charging();
mqtt_v.publish(msg.replyTo, result);
return true;
}
return false;
});
}
void evse_manager_consumer_API::generate_api_cmd_stop_transaction() {
helper.subscribe_api_topic("stop_transaction", [this](std::string const& data) {
auto result = false;
API_generic::RequestReply msg;
if (deserialize(data, msg)) {
API_types_ext::StopTransactionRequest_External payload;
if (deserialize(msg.payload, payload)) {
result = r_evse_manager->call_stop_transaction(API_types_ext::to_internal_api(payload));
mqtt_v.publish(msg.replyTo, result);
return true;
}
}
return false;
});
}
void evse_manager_consumer_API::generate_api_cmd_force_unlock() {
helper.subscribe_api_topic("force_unlock", [this](std::string const& data) {
API_generic::RequestReply msg;
if (deserialize(data, msg)) {
int payload;
if (deserialize(msg.payload, payload)) {
auto result = r_evse_manager->call_force_unlock(payload);
mqtt_v.publish(msg.replyTo, result);
}
return true;
}
return false;
});
}
void evse_manager_consumer_API::generate_api_cmd_random_delay_enable() {
if (not r_random_delay.empty()) {
helper.subscribe_api_topic("random_delay_enable", [this](std::string const&) {
r_random_delay[0]->call_enable();
return true;
});
}
}
void evse_manager_consumer_API::generate_api_cmd_random_delay_disable() {
if (not r_random_delay.empty()) {
helper.subscribe_api_topic("random_delay_disable", [=](std::string const&) {
r_random_delay[0]->call_disable();
return true;
});
}
}
void evse_manager_consumer_API::generate_api_cmd_random_delay_cancel() {
if (not r_random_delay.empty()) {
helper.subscribe_api_topic("random_delay_cancel", [=](std::string const&) {
r_random_delay[0]->call_cancel();
return true;
});
}
}
void evse_manager_consumer_API::generate_api_cmd_random_delay_set_duration_s() {
if (not r_random_delay.empty())
helper.subscribe_api_topic("random_delay_set_duration_s", [=](std::string const& data) {
int32_t duration;
if (deserialize(data, duration)) {
r_random_delay[0]->call_set_duration_s(duration);
return true;
}
return false;
});
}
void evse_manager_consumer_API::generate_api_var_session_event() {
r_evse_manager->subscribe_session_event(forward_api_var("session_event"));
}
void evse_manager_consumer_API::generate_api_var_hlc_session_failed() {
r_evse_manager->subscribe_hlc_session_failed(forward_api_var("hlc_session_failed"));
}
void evse_manager_consumer_API::generate_api_var_ev_info() {
r_evse_manager->subscribe_ev_info(forward_api_var("ev_info"));
}
void evse_manager_consumer_API::generate_api_var_powermeter() {
r_evse_manager->subscribe_powermeter(forward_api_var("powermeter"));
}
void evse_manager_consumer_API::generate_api_var_evse_id() {
r_evse_manager->subscribe_evse_id(forward_api_var("evse_id"));
}
void evse_manager_consumer_API::generate_api_var_hw_capabilities() {
r_evse_manager->subscribe_hw_capabilities(forward_api_var("hw_capabilities"));
}
void evse_manager_consumer_API::generate_api_var_enforced_limits() {
r_evse_manager->subscribe_enforced_limits(forward_api_var("enforced_limits"));
}
void evse_manager_consumer_API::generate_api_var_selected_protocol() {
r_evse_manager->subscribe_selected_protocol(forward_api_var("selected_protocol"));
}
void evse_manager_consumer_API::generate_api_var_supported_energy_transfer_modes() {
r_evse_manager->subscribe_supported_energy_transfer_modes(forward_api_var("supported_energy_transfer_modes"));
}
void evse_manager_consumer_API::generate_api_var_powermeter_public_key_ocmf() {
r_evse_manager->subscribe_powermeter_public_key_ocmf(forward_api_var("powermeter_public_key_ocmf"));
}
void evse_manager_consumer_API::generate_api_var_ac_nr_of_phases_available() {
if (not r_evse_bsp.empty()) {
r_evse_bsp[0]->subscribe_ac_nr_of_phases_available(forward_api_var("ac_nr_of_phases_available"));
}
}
void evse_manager_consumer_API::generate_api_var_ac_pp_ampacity() {
if (not r_evse_bsp.empty()) {
r_evse_bsp[0]->subscribe_ac_pp_ampacity(forward_api_var("ac_pp_ampacity"));
}
}
void evse_manager_consumer_API::generate_api_var_dlink_ready() {
if (not r_slac.empty()) {
r_slac[0]->subscribe_dlink_ready(forward_api_var("dlink_ready"));
}
}
void evse_manager_consumer_API::generate_api_var_isolation_measurement() {
if (not r_imd.empty()) {
r_imd[0]->subscribe_isolation_measurement(forward_api_var("isolation_measurement"));
}
}
void evse_manager_consumer_API::generate_api_var_dc_voltage_current() {
if (not r_ps_dc.empty()) {
r_ps_dc[0]->subscribe_voltage_current(forward_api_var("dc_voltage_current"));
}
}
void evse_manager_consumer_API::generate_api_var_dc_mode() {
if (not r_ps_dc.empty()) {
r_ps_dc[0]->subscribe_mode(forward_api_var("dc_mode"));
}
}
void evse_manager_consumer_API::generate_api_var_dc_capabilities() {
if (not r_ps_dc.empty()) {
r_ps_dc[0]->subscribe_capabilities(forward_api_var("dc_capabilities"));
}
}
void evse_manager_consumer_API::generate_api_var_random_delay_countdown() {
if (not r_random_delay.empty()) {
r_random_delay[0]->subscribe_countdown(forward_api_var("random_delay_countdown"));
}
}
void evse_manager_consumer_API::generate_api_var_session_info() {
this->session_info.handle()->set_publish_callback(
[this](const everest::lib::API::V1_0::types::evse_manager::SessionInfo& external) {
static const auto topic = helper.get_topics().everest_to_extern("session_info");
try {
auto&& payload = serialize(external);
mqtt_v.publish(topic, payload);
} catch (const std::exception& e) {
EVLOG_warning << "Variable: '" << topic << "' failed with -> " << e.what();
} catch (...) {
EVLOG_warning << "Invalid data: Cannot convert internal to external or serialize it.\n" << topic;
}
});
this->r_evse_manager->subscribe_session_event([this](types::evse_manager::SessionEvent const& session_event) {
session_info.handle()->update_state(session_event);
});
this->r_evse_manager->subscribe_powermeter([this](types::powermeter::Powermeter const& powermeter) {
session_info.handle()->update_powermeter(powermeter);
});
this->r_evse_manager->subscribe_selected_protocol(
[this](std::string const& protocol) { session_info.handle()->update_selected_protocol(protocol); });
}
} // namespace module

View File

@@ -0,0 +1,141 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
#ifndef EVSE_MANAGER_CONSUMER_API_HPP
#define EVSE_MANAGER_CONSUMER_API_HPP
//
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
// template version 2
//
#include "ld-ev.hpp"
// headers for provided interface implementations
#include <generated/interfaces/generic_error/Implementation.hpp>
// headers for required interface implementations
#include <generated/interfaces/evse_board_support/Interface.hpp>
#include <generated/interfaces/evse_manager/Interface.hpp>
#include <generated/interfaces/isolation_monitor/Interface.hpp>
#include <generated/interfaces/power_supply_DC/Interface.hpp>
#include <generated/interfaces/slac/Interface.hpp>
#include <generated/interfaces/uk_random_delay/Interface.hpp>
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
// insert your custom include headers here
#include <everest/util/async/monitor.hpp>
#include <everest_api_module_helpers/ApiHelper.hpp>
#include <everest_api_types/entrypoint/API.hpp>
#include "session_info.hpp"
namespace ev_API = everest::lib::API;
namespace API_types = ev_API::V1_0::types;
namespace API_types_entry = API_types::entrypoint;
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
namespace module {
struct Conf {
int cfg_communication_check_to_s;
int cfg_heartbeat_interval_ms;
int cfg_request_reply_to_s;
};
class evse_manager_consumer_API : public Everest::ModuleBase {
public:
evse_manager_consumer_API() = delete;
evse_manager_consumer_API(const ModuleInfo& info, Everest::MqttProvider& mqtt_provider,
std::unique_ptr<generic_errorImplBase> p_main,
std::unique_ptr<evse_managerIntf> r_evse_manager,
std::vector<std::unique_ptr<evse_board_supportIntf>> r_evse_bsp,
std::vector<std::unique_ptr<slacIntf>> r_slac,
std::vector<std::unique_ptr<isolation_monitorIntf>> r_imd,
std::vector<std::unique_ptr<power_supply_DCIntf>> r_ps_dc,
std::vector<std::unique_ptr<uk_random_delayIntf>> r_random_delay, Conf& config) :
ModuleBase(info),
mqtt(mqtt_provider),
p_main(std::move(p_main)),
r_evse_manager(std::move(r_evse_manager)),
r_evse_bsp(std::move(r_evse_bsp)),
r_slac(std::move(r_slac)),
r_imd(std::move(r_imd)),
r_ps_dc(std::move(r_ps_dc)),
r_random_delay(std::move(r_random_delay)),
config(config){};
Everest::MqttProvider& mqtt;
const std::unique_ptr<generic_errorImplBase> p_main;
const std::unique_ptr<evse_managerIntf> r_evse_manager;
const std::vector<std::unique_ptr<evse_board_supportIntf>> r_evse_bsp;
const std::vector<std::unique_ptr<slacIntf>> r_slac;
const std::vector<std::unique_ptr<isolation_monitorIntf>> r_imd;
const std::vector<std::unique_ptr<power_supply_DCIntf>> r_ps_dc;
const std::vector<std::unique_ptr<uk_random_delayIntf>> r_random_delay;
const Conf& config;
// ev@1fce4c5e-0ab8-41bb-90f7-14277703d2ac:v1
// insert your public definitions here
ev_API::Mqtt::ValidatingMqttProxy mqtt_v{mqtt};
ev_API::ApiHelper helper{info, mqtt_v, {{"evse_manager_consumer", 1}}, get_config_service_client()};
// 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
auto forward_api_var(std::string const& var);
void generate_api_cmd_get_evse();
void generate_api_cmd_enable_disable();
void generate_api_cmd_pause_charging();
void generate_api_cmd_resume_charging();
void generate_api_cmd_stop_transaction();
void generate_api_cmd_force_unlock();
void generate_api_cmd_random_delay_enable();
void generate_api_cmd_random_delay_disable();
void generate_api_cmd_random_delay_cancel();
void generate_api_cmd_random_delay_set_duration_s();
void generate_api_var_session_event();
void generate_api_var_hlc_session_failed();
void generate_api_var_session_info();
void generate_api_var_ev_info();
void generate_api_var_powermeter();
void generate_api_var_evse_id();
void generate_api_var_hw_capabilities();
void generate_api_var_enforced_limits();
void generate_api_var_selected_protocol();
void generate_api_var_powermeter_public_key_ocmf();
void generate_api_var_supported_energy_transfer_modes();
void generate_api_var_ac_nr_of_phases_available();
void generate_api_var_ac_pp_ampacity();
void generate_api_var_dlink_ready();
void generate_api_var_isolation_measurement();
void generate_api_var_dc_voltage_current();
void generate_api_var_dc_mode();
void generate_api_var_dc_capabilities();
void generate_api_var_random_delay_countdown();
ev_API::CommCheckHandler<generic_errorImplBase> comm_check{"generic/CommunicationFault",
ev_API::bridge_connection_lost_message, p_main};
everest::lib::util::monitor<SessionInfo> session_info;
// 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 // EVSE_MANAGER_CONSUMER_API_HPP

View File

@@ -0,0 +1,16 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
#include "generic_errorImpl.hpp"
namespace module {
namespace main {
void generic_errorImpl::init() {
}
void generic_errorImpl::ready() {
}
} // namespace main
} // namespace module

View File

@@ -0,0 +1,61 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
#ifndef MAIN_GENERIC_ERROR_IMPL_HPP
#define MAIN_GENERIC_ERROR_IMPL_HPP
//
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
// template version 3
//
#include <generated/interfaces/generic_error/Implementation.hpp>
#include "../evse_manager_consumer_API.hpp"
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
// insert your custom include headers here
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
namespace module {
namespace main {
struct Conf {};
class generic_errorImpl : public generic_errorImplBase {
public:
generic_errorImpl() = delete;
generic_errorImpl(Everest::ModuleAdapter* ev, const Everest::PtrContainer<evse_manager_consumer_API>& mod,
Conf& config) :
generic_errorImplBase(ev, "main"), mod(mod), config(config){};
// ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1
// insert your public definitions here
// ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1
protected:
// no commands defined for this interface
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
// insert your protected definitions here
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
private:
const Everest::PtrContainer<evse_manager_consumer_API>& mod;
const Conf& config;
virtual void init() override;
virtual void ready() override;
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
// insert your private definitions here
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
};
// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1
// insert other definitions here
// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1
} // namespace main
} // namespace module
#endif // MAIN_GENERIC_ERROR_IMPL_HPP

View File

@@ -0,0 +1,53 @@
description: API for using EVSE manager (consumer API)
config:
cfg_communication_check_to_s:
description: "Maximum time between two communication check events. Values <= 0 disables communication checks."
type: integer
default: 5
cfg_heartbeat_interval_ms:
description: "Interval between two heartbeat messages send by the API. Values <= 0 disable heartbeat."
type: integer
default: 1000
cfg_request_reply_to_s:
description: "Maximum time between request and reply. After timeout the request is answered with a default response."
type: integer
default: 550
minimum: 1
maximum: 550
provides:
main:
interface: generic_error
description: "Provides errors types for module communication status."
requires:
evse_manager:
interface: evse_manager
evse_bsp:
interface: evse_board_support
min_connections: 0
max_connections: 1
slac:
interface: slac
min_connections: 0
max_connections: 1
imd:
interface: isolation_monitor
min_connections: 0
max_connections: 1
ps_dc:
interface: power_supply_DC
min_connections: 0
max_connections: 1
random_delay:
interface: uk_random_delay
min_connections: 0
max_connections: 1
enable_external_mqtt: true
enable_global_errors: true
metadata:
license: https://opensource.org/licenses/Apache-2.0
authors:
- James Chapman
- Jan Christoph Habig
- Florin Mihut

View File

@@ -0,0 +1,231 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
#include "session_info.hpp"
#include <everest/logging.hpp>
#include <utils/date.hpp>
namespace module {
using namespace everest::lib::API::V1_0::types::evse_manager;
SessionInfo::SessionInfo() :
publish_cb([](auto) {}),
start_energy_import_wh(0),
end_energy_import_wh(0),
start_energy_export_wh(0),
end_energy_export_wh(0) {
this->session_start_time_point = date::utc_clock::now();
this->session_end_time_point = this->session_start_time_point;
this->transaction_start_time_point = this->session_start_time_point;
this->transaction_end_time_point = this->session_start_time_point;
}
void SessionInfo::set_publish_callback(PublishCallback cb) {
this->publish_cb = [cb](everest::lib::API::V1_0::types::evse_manager::SessionInfo ext) {
ext.timestamp = Everest::Date::to_rfc3339(date::utc_clock::now());
cb(ext);
};
}
void SessionInfo::update_state(const types::evse_manager::SessionEvent& session_event) {
using Event = types::evse_manager::SessionEventEnum;
try {
switch (session_event.event) {
case Event::Enabled:
this->ext.state = EvseStateEnum::Unplugged;
break;
case Event::Disabled:
this->ext.state = EvseStateEnum::Disabled;
break;
case Event::AuthRequired:
this->ext.state = EvseStateEnum::AuthRequired;
break;
case Event::SessionStarted:
this->handle_session_started(session_event);
this->ext.state = EvseStateEnum::Preparing;
break;
case Event::PrepareCharging:
this->ext.state = EvseStateEnum::Preparing;
break;
case Event::TransactionStarted:
this->handle_transaction_started(session_event);
break;
case Event::ChargingStarted:
this->ext.state = EvseStateEnum::Charging;
break;
case Event::ChargingPausedEV:
this->ext.state = EvseStateEnum::ChargingPausedEV;
break;
case Event::ChargingPausedEVSE:
this->ext.state = EvseStateEnum::ChargingPausedEVSE;
break;
case Event::ChargingFinished:
this->ext.state = EvseStateEnum::Finished;
break;
case Event::StoppingCharging:
this->ext.state = EvseStateEnum::FinishedEV;
break;
case Event::TransactionFinished: {
this->handle_transaction_finished(session_event);
break;
}
case Event::PluginTimeout:
this->ext.state = EvseStateEnum::AuthTimeout;
break;
case Event::SessionFinished:
this->handle_session_finished(session_event);
break;
case Event::ReservationStart:
case Event::ReservationEnd:
default:
break;
}
publish_cb(this->ext);
} catch (const std::exception& e) {
EVLOG_warning << "Session event handling failed with -> " << e.what();
}
}
void SessionInfo::update_powermeter(const types::powermeter::Powermeter& powermeter) {
try {
this->set_latest_energy_import_wh(powermeter.energy_Wh_import.total);
if (powermeter.energy_Wh_export.has_value()) {
this->set_latest_energy_export_wh(powermeter.energy_Wh_export.value().total);
}
if (powermeter.power_W.has_value()) {
this->ext.latest_total_w = powermeter.power_W.value().total;
}
if (this->is_session_running()) {
publish_cb(this->ext);
}
} catch (const std::exception& e) {
EVLOG_warning << "Powermeter update handling failed with -> " << e.what();
}
}
void SessionInfo::update_selected_protocol(const std::string& protocol) {
try {
this->ext.selected_protocol = protocol;
if (this->is_session_running()) {
publish_cb(this->ext);
}
} catch (const std::exception& e) {
EVLOG_warning << "Selected protocol update handling failed with -> " << e.what();
}
}
void SessionInfo::handle_session_started(const types::evse_manager::SessionEvent& session_event) {
this->session_start_time_point = Everest::Date::from_rfc3339(session_event.timestamp);
this->session_end_time_point = this->session_start_time_point;
this->start_energy_import_wh = this->end_energy_import_wh;
this->start_energy_export_wh = this->end_energy_export_wh;
this->ext.state = EvseStateEnum::Unknown;
this->ext.charged_energy_wh = 0;
this->ext.discharged_energy_wh = 0;
this->ext.session_duration_s = 0;
this->ext.transaction_duration_s.reset();
this->ext.latest_total_w = 0;
this->ext.selected_protocol.reset();
this->ext.transaction_start_time.reset();
this->ext.transaction_end_time.reset();
this->ext.session_end_time.reset();
this->ext.session_start_time = Everest::Date::to_rfc3339(this->session_start_time_point);
}
void SessionInfo::handle_session_finished(const types::evse_manager::SessionEvent& session_event) {
this->ext.session_end_time = session_event.timestamp;
this->ext.state = EvseStateEnum::Unplugged;
}
void SessionInfo::handle_transaction_started(const types::evse_manager::SessionEvent& session_event) {
this->ext.state = EvseStateEnum::Preparing;
this->transaction_running = true;
if (!session_event.transaction_started.has_value()) {
return;
}
auto transaction_started = session_event.transaction_started.value();
this->transaction_start_time_point = Everest::Date::from_rfc3339(session_event.timestamp);
this->transaction_end_time_point = this->transaction_start_time_point;
this->start_energy_import_wh = transaction_started.meter_value.energy_Wh_import.total;
this->end_energy_import_wh = this->start_energy_import_wh;
this->transaction_end_time_point = this->transaction_start_time_point;
if (transaction_started.meter_value.energy_Wh_export.has_value()) {
auto energy_Wh_export = transaction_started.meter_value.energy_Wh_export.value().total;
this->start_energy_export_wh = energy_Wh_export;
this->end_energy_export_wh = energy_Wh_export;
this->start_energy_export_wh_was_set = true;
} else {
this->start_energy_export_wh_was_set = false;
}
this->ext.transaction_start_time = Everest::Date::to_rfc3339(this->transaction_start_time_point);
}
void SessionInfo::handle_transaction_finished(const types::evse_manager::SessionEvent& session_event) {
this->ext.state = EvseStateEnum::Finished;
if (!session_event.transaction_finished.has_value()) {
return;
}
auto transaction_finished = session_event.transaction_finished.value();
if (transaction_finished.reason == types::evse_manager::StopTransactionReason::Local) {
this->ext.state = EvseStateEnum::FinishedEVSE;
}
auto energy_Wh_import = transaction_finished.meter_value.energy_Wh_import.total;
this->end_energy_import_wh = energy_Wh_import;
this->transaction_end_time_point = Everest::Date::from_rfc3339(session_event.timestamp);
this->transaction_running = false;
if (transaction_finished.meter_value.energy_Wh_export.has_value()) {
auto energy_Wh_export = transaction_finished.meter_value.energy_Wh_export.value().total;
this->end_energy_export_wh = energy_Wh_export;
this->end_energy_export_wh_was_set = true;
} else {
this->end_energy_export_wh_was_set = false;
}
this->ext.transaction_end_time = Everest::Date::to_rfc3339(this->transaction_end_time_point);
}
void SessionInfo::set_latest_energy_import_wh(int32_t latest_energy_wh_import) {
this->ext.charged_energy_wh = this->end_energy_import_wh - this->start_energy_import_wh;
if (this->start_energy_export_wh_was_set && this->end_energy_export_wh_was_set) {
this->ext.discharged_energy_wh = this->end_energy_export_wh - this->start_energy_export_wh;
}
this->ext.session_duration_s =
std::chrono::duration_cast<std::chrono::seconds>(this->session_end_time_point - this->session_start_time_point)
.count();
this->session_end_time_point = date::utc_clock::now();
if (transaction_running) {
this->ext.transaction_duration_s = std::chrono::duration_cast<std::chrono::seconds>(
this->transaction_end_time_point - this->transaction_start_time_point)
.count();
this->transaction_end_time_point = this->session_end_time_point;
this->end_energy_import_wh = latest_energy_wh_import;
}
}
void SessionInfo::set_latest_energy_export_wh(int32_t latest_export_energy_wh) {
this->end_energy_export_wh = latest_export_energy_wh;
this->end_energy_export_wh_was_set = true;
}
bool SessionInfo::is_session_running() {
return this->ext.state != EvseStateEnum::Unplugged && this->ext.state != EvseStateEnum::Disabled and
this->ext.state != EvseStateEnum::Unknown;
}
} // namespace module

View File

@@ -0,0 +1,57 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
#pragma once
#include <chrono>
#include <date/date.h>
#include <date/tz.h>
#include <mutex>
#include <string>
#include <everest_api_types/evse_manager/API.hpp>
#include <generated/types/evse_manager.hpp>
namespace module {
/// \brief Session and transaction information for EVSE
class SessionInfo {
public:
using PublishCallback = std::function<void(everest::lib::API::V1_0::types::evse_manager::SessionInfo)>;
SessionInfo();
void set_publish_callback(PublishCallback cb);
void update_state(const types::evse_manager::SessionEvent& session_event);
void update_powermeter(const types::powermeter::Powermeter& powermeter);
void update_selected_protocol(const std::string& protocol);
private:
PublishCallback publish_cb;
/// \brief External API representation
everest::lib::API::V1_0::types::evse_manager::SessionInfo ext;
bool start_energy_export_wh_was_set{
false}; ///< Indicate if start export energy value (optional) has been received or not
bool end_energy_export_wh_was_set{
false}; ///< Indicate if end export energy value (optional) has been received or not
bool transaction_running{false};
int32_t start_energy_import_wh; ///< Energy reading (import) at the beginning of this charging session in Wh
int32_t end_energy_import_wh; ///< Energy reading (import) at the end of this charging session in Wh
int32_t start_energy_export_wh; ///< Energy reading (export) at the beginning of this charging session in Wh
int32_t end_energy_export_wh; ///< Energy reading (export) at the end of this charging session in Wh
std::chrono::time_point<date::utc_clock> session_start_time_point; ///< Start of the charging session
std::chrono::time_point<date::utc_clock> session_end_time_point; ///< End of the charging session
std::chrono::time_point<date::utc_clock> transaction_start_time_point; ///< Start of the transaction
std::chrono::time_point<date::utc_clock> transaction_end_time_point; ///< End of the transaction
void handle_session_started(const types::evse_manager::SessionEvent& session_event);
void handle_session_finished(const types::evse_manager::SessionEvent& session_event);
void handle_transaction_started(const types::evse_manager::SessionEvent& session_event);
void handle_transaction_finished(const types::evse_manager::SessionEvent& session_event);
void set_latest_energy_import_wh(int32_t latest_energy_wh_import);
void set_latest_energy_export_wh(int32_t latest_export_energy_wh);
bool is_session_running();
};
} // namespace module

View File

@@ -0,0 +1,31 @@
#
# 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_compile_options(${MODULE_NAME}
PUBLIC -Wall -Wextra -pedantic -Werror=switch)
target_link_libraries(${MODULE_NAME}
PRIVATE
atomic
everest::everest_api_types
everest::everest_api_module_helpers
)
# ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1
target_sources(${MODULE_NAME}
PRIVATE
"main/generic_errorImpl.cpp"
)
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1
# insert other things like install cmds etc here
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1

View File

@@ -0,0 +1,11 @@
.. _everest_modules_handwritten_evse_security_consumer_API:
.. *******************************************
.. evse_security_consumer_API
.. *******************************************
The complete API specification can be found in the
``docs/source/reference/EVerest_API/evse_security_consumer_API.yaml``
file in the source repository, or in the `AsyncAPI HTML documentation <../../../api/evse_security_consumer_API/index.html>`_ automatically generated from it.

View File

@@ -0,0 +1,94 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
#include "evse_security_consumer_API.hpp"
#include <everest_api_types/evse_security/API.hpp>
#include <everest_api_types/evse_security/codec.hpp>
#include <everest_api_types/evse_security/wrapper.hpp>
#include <everest_api_types/generic/codec.hpp>
#include <everest_api_types/utilities/codec.hpp>
#include <utility>
namespace module {
namespace API_types_ext = API_types::evse_security;
namespace API_generic = API_types::generic;
using ev_API::deserialize;
void evse_security_consumer_API::init() {
invoke_init(*p_main);
API_types_entry::CommunicationParameters comm_params{};
comm_params.heartbeat_period_ms = config.cfg_heartbeat_interval_ms;
comm_params.communication_check_period_s = config.cfg_communication_check_to_s;
helper.init(comm_params);
}
void evse_security_consumer_API::ready() {
invoke_ready(*p_main);
generate_api_cmd_is_ca_certificate_installed();
generate_api_cmd_get_leaf_certificate_info();
generate_api_cmd_get_verify_location();
helper.generate_api_var_communication_check(&comm_check);
comm_check.start(config.cfg_communication_check_to_s);
helper.setup_heartbeat_generator(&comm_check, config.cfg_heartbeat_interval_ms);
helper.publish_ready_beacon();
}
void evse_security_consumer_API::generate_api_cmd_is_ca_certificate_installed() {
helper.subscribe_api_topic("is_ca_certificate_installed", [this](std::string const& data) {
API_generic::RequestReply msg;
if (deserialize(data, msg)) {
API_types_ext::CaCertificateType payload;
if (deserialize(msg.payload, payload)) {
bool response = r_evse_security->call_is_ca_certificate_installed(to_internal_api(payload));
mqtt_v.publish(msg.replyTo, response);
if (response) {
mqtt_v.publish(msg.replyTo, "true");
} else {
mqtt_v.publish(msg.replyTo, "false");
}
return true;
}
}
return false;
});
}
void evse_security_consumer_API::generate_api_cmd_get_leaf_certificate_info() {
helper.subscribe_api_topic("get_leaf_certificate_info", [this](std::string const& data) {
API_generic::RequestReply msg;
if (deserialize(data, msg)) {
API_types_ext::GetLeafCertificateInfoRequest payload;
if (deserialize(msg.payload, payload)) {
auto int_res = r_evse_security->call_get_leaf_certificate_info(
to_internal_api(payload.certificate_type), to_internal_api(payload.encoding), payload.include_ocsp);
auto ext_res = API_types_ext::to_external_api(int_res);
mqtt_v.publish(msg.replyTo, serialize(ext_res));
return true;
}
}
return false;
});
}
void evse_security_consumer_API::generate_api_cmd_get_verify_location() {
helper.subscribe_api_topic("get_verify_location", [this](std::string const& data) {
API_generic::RequestReply msg;
if (deserialize(data, msg)) {
API_types_ext::CaCertificateType payload;
if (deserialize(msg.payload, payload)) {
auto response = r_evse_security->call_get_verify_location(to_internal_api(payload));
mqtt_v.publish(msg.replyTo, response);
return true;
}
}
return false;
});
}
} // namespace module

View File

@@ -0,0 +1,88 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
#ifndef EVSE_SECURITY_CONSUMER_API_HPP
#define EVSE_SECURITY_CONSUMER_API_HPP
//
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
// template version 2
//
#include "ld-ev.hpp"
// headers for provided interface implementations
#include <generated/interfaces/generic_error/Implementation.hpp>
// headers for required interface implementations
#include <generated/interfaces/evse_security/Interface.hpp>
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
// insert your custom include headers here
#include <everest_api_module_helpers/ApiHelper.hpp>
#include <everest_api_types/utilities/Topics.hpp>
namespace ev_API = everest::lib::API;
namespace API_types = ev_API::V1_0::types;
namespace API_types_entry = API_types::entrypoint;
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
namespace module {
struct Conf {
int cfg_communication_check_to_s;
int cfg_heartbeat_interval_ms;
};
class evse_security_consumer_API : public Everest::ModuleBase {
public:
evse_security_consumer_API() = delete;
evse_security_consumer_API(const ModuleInfo& info, Everest::MqttProvider& mqtt_provider,
std::unique_ptr<generic_errorImplBase> p_main,
std::unique_ptr<evse_securityIntf> r_evse_security, Conf& config) :
ModuleBase(info),
mqtt(mqtt_provider),
p_main(std::move(p_main)),
r_evse_security(std::move(r_evse_security)),
config(config){};
Everest::MqttProvider& mqtt;
const std::unique_ptr<generic_errorImplBase> p_main;
const std::unique_ptr<evse_securityIntf> r_evse_security;
const Conf& config;
// ev@1fce4c5e-0ab8-41bb-90f7-14277703d2ac:v1
// insert your public definitions here
ev_API::Mqtt::ValidatingMqttProxy mqtt_v{mqtt};
ev_API::ApiHelper helper{info, mqtt_v, {{"evse_security_consumer", 1}}, get_config_service_client()};
// 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
void generate_api_cmd_is_ca_certificate_installed();
void generate_api_cmd_get_leaf_certificate_info();
void generate_api_cmd_get_verify_location();
void generate_api_var_token_validation_status();
ev_API::CommCheckHandler<generic_errorImplBase> comm_check{"generic/CommunicationFault",
ev_API::bridge_connection_lost_message, p_main};
// 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 // EVSE_SECURITY_CONSUMER_API_HPP

View File

@@ -0,0 +1,16 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
#include "generic_errorImpl.hpp"
namespace module {
namespace main {
void generic_errorImpl::init() {
}
void generic_errorImpl::ready() {
}
} // namespace main
} // namespace module

View File

@@ -0,0 +1,61 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
#ifndef MAIN_GENERIC_ERROR_IMPL_HPP
#define MAIN_GENERIC_ERROR_IMPL_HPP
//
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
// template version 3
//
#include <generated/interfaces/generic_error/Implementation.hpp>
#include "../evse_security_consumer_API.hpp"
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
// insert your custom include headers here
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
namespace module {
namespace main {
struct Conf {};
class generic_errorImpl : public generic_errorImplBase {
public:
generic_errorImpl() = delete;
generic_errorImpl(Everest::ModuleAdapter* ev, const Everest::PtrContainer<evse_security_consumer_API>& mod,
Conf& config) :
generic_errorImplBase(ev, "main"), mod(mod), config(config){};
// ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1
// insert your public definitions here
// ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1
protected:
// no commands defined for this interface
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
// insert your protected definitions here
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
private:
const Everest::PtrContainer<evse_security_consumer_API>& mod;
const Conf& config;
virtual void init() override;
virtual void ready() override;
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
// insert your private definitions here
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
};
// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1
// insert other definitions here
// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1
} // namespace main
} // namespace module
#endif // MAIN_GENERIC_ERROR_IMPL_HPP

View File

@@ -0,0 +1,24 @@
description: API for evse_security (consumer API)
config:
cfg_communication_check_to_s:
description: "Maximum time between two communication check events. Values <= 0 disables communication checks."
type: integer
default: 5
cfg_heartbeat_interval_ms:
description: "Interval between two heartbeat messages send by the API. Values <= 0 disable heartbeat."
type: integer
default: 1000
provides:
main:
interface: generic_error
description: "Provides generic errors for communication check"
requires:
evse_security:
interface: evse_security
enable_external_mqtt: true
metadata:
license: https://opensource.org/licenses/Apache-2.0
authors:
- Christoph Burandt

View File

@@ -0,0 +1,31 @@
#
# 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_compile_options(${MODULE_NAME}
PUBLIC -Wall -Wextra -pedantic -Werror=switch)
target_link_libraries(${MODULE_NAME}
PRIVATE
atomic
everest::everest_api_types
everest::everest_api_module_helpers
)
# ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1
target_sources(${MODULE_NAME}
PRIVATE
"main/generic_errorImpl.cpp"
)
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1
# insert other things like install cmds etc here
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1

View File

@@ -0,0 +1,11 @@
.. _everest_modules_handwritten_external_energy_limits_consumer_API:
.. *******************************************
.. external_energy_limits_consumer_API
.. *******************************************
The complete API specification can be found in the
``docs/source/reference/EVerest_API/external_energy_limits_consumer_API.yaml``
file in the source repository, or in the `AsyncAPI HTML documentation <../../../../api/external_energy_limits_consumer_API/index.html>`_ automatically generated from it.

View File

@@ -0,0 +1,72 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
#include "external_energy_limits_consumer_API.hpp"
#include <everest_api_types/energy/API.hpp>
#include <everest_api_types/energy/codec.hpp>
#include <everest_api_types/energy/wrapper.hpp>
#include <everest_api_types/generic/codec.hpp>
#include <everest_api_types/utilities/codec.hpp>
namespace module {
namespace API_types = everest::lib::API::V1_0::types;
namespace API_types_ext = API_types::energy;
namespace API_generic = API_types::generic;
using ev_API::deserialize;
void external_energy_limits_consumer_API::init() {
invoke_init(*p_main);
API_types_entry::CommunicationParameters comm_params{};
comm_params.heartbeat_period_ms = config.cfg_heartbeat_interval_ms;
comm_params.communication_check_period_s = config.cfg_communication_check_to_s;
helper.init(comm_params);
}
void external_energy_limits_consumer_API::ready() {
invoke_ready(*p_main);
generate_api_var_capabilities();
generate_api_cmd_set_external_limits();
helper.generate_api_var_communication_check(&comm_check);
comm_check.start(config.cfg_communication_check_to_s);
helper.setup_heartbeat_generator(&comm_check, config.cfg_heartbeat_interval_ms);
helper.publish_ready_beacon();
}
auto external_energy_limits_consumer_API::forward_api_var(std::string const& var) {
using namespace API_types_ext;
const auto topic = helper.get_topics().everest_to_extern(var);
return [this, topic](auto const& val) {
try {
auto&& external = to_external_api(val);
auto&& payload = serialize(external);
mqtt_v.publish(topic, payload);
} catch (const std::exception& e) {
EVLOG_warning << "Variable: '" << topic << "' failed with -> " << e.what();
} catch (...) {
EVLOG_warning << "Invalid data: Cannot convert internal to external or serialize it.\n" << topic;
}
};
}
void external_energy_limits_consumer_API::generate_api_cmd_set_external_limits() {
helper.subscribe_api_topic("set_external_limits", [this](std::string const& data) {
API_types_ext::ExternalLimits val;
if (deserialize(data, val)) {
r_energy_node->call_set_external_limits(to_internal_api(val));
return true;
}
return false;
});
}
void external_energy_limits_consumer_API::generate_api_var_capabilities() {
r_energy_node->subscribe_capabilities(forward_api_var("capabilities"));
}
} // namespace module

Some files were not shown because too many files have changed in this diff Show More