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

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

384 lines
17 KiB
C++

// SPDX-License-Identifier: Apache-2.0
// Copyright chargebyte GmbH and Contributors to EVerest
#include <cmath>
#include <everest/external_energy_limits/external_energy_limits.hpp>
#include <everest/logging.hpp>
#include <utils/date.hpp>
#include "RpcApiRequestHandler.hpp"
using namespace types::json_rpc_api;
static const std::string RPCAPI_MODULE_SOURCE = "RpcApi_module";
static const std::chrono::seconds CURRENT_LIMIT_APPLY_TIMEOUT{5};
static types::energy::ExternalLimits get_external_limits(int32_t phases) {
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.total_power_W = {0, RPCAPI_MODULE_SOURCE};
target_entry.limits_to_leaves.ac_max_phase_count = {phases, RPCAPI_MODULE_SOURCE};
target_entry.limits_to_leaves.ac_min_phase_count = {phases, RPCAPI_MODULE_SOURCE};
external_limits.schedule_import = std::vector<types::energy::ScheduleReqEntry>(1, target_entry);
external_limits.schedule_export = std::vector<types::energy::ScheduleReqEntry>(1, zero_entry);
return external_limits;
}
// This function is used to get the external limits for AC charging based on the current or power value.
static types::energy::ExternalLimits get_external_limits(float phy_value, bool is_power = false) {
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.total_power_W = {0, RPCAPI_MODULE_SOURCE};
if (is_power) {
target_entry.limits_to_leaves.total_power_W = {std::abs(phy_value), RPCAPI_MODULE_SOURCE};
} else {
target_entry.limits_to_leaves.ac_max_current_A = {std::abs(phy_value), RPCAPI_MODULE_SOURCE};
}
if (phy_value > 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_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 types::energy::ExternalLimits get_external_limits(float phy_value, bool is_power, int32_t phases) {
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;
target_entry.limits_to_leaves.ac_max_phase_count = {phases, RPCAPI_MODULE_SOURCE};
target_entry.limits_to_leaves.ac_min_phase_count = {phases, RPCAPI_MODULE_SOURCE};
if (is_power) {
target_entry.limits_to_leaves.total_power_W = {std::abs(phy_value), RPCAPI_MODULE_SOURCE};
zero_entry.limits_to_leaves.total_power_W = {0, RPCAPI_MODULE_SOURCE};
} else {
target_entry.limits_to_leaves.ac_max_current_A = {std::abs(phy_value), RPCAPI_MODULE_SOURCE};
zero_entry.limits_to_leaves.ac_max_current_A = {0, RPCAPI_MODULE_SOURCE};
}
if (phy_value > 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_export = std::vector<types::energy::ScheduleReqEntry>(1, target_entry);
external_limits.schedule_import = std::vector<types::energy::ScheduleReqEntry>(1, zero_entry);
}
return external_limits;
}
RpcApiRequestHandler::RpcApiRequestHandler(
data::DataStoreCharger& dataobj, const std::vector<std::unique_ptr<evse_managerIntf>>& r_evse_managers,
const std::vector<std::unique_ptr<external_energy_limitsIntf>>& r_evse_energy_sink) :
data_store(dataobj), evse_managers(r_evse_managers), evse_energy_sink(r_evse_energy_sink) {
}
ErrorResObj RpcApiRequestHandler::set_charging_allowed(const int32_t evse_index, bool charging_allowed) {
ErrorResObj res{};
bool success{true};
// find the EVSE manager for the given index
const auto it = std::find_if(evse_managers.begin(), evse_managers.end(), [&evse_index](const auto& manager) {
const auto mapping = manager->get_mapping();
return (mapping.has_value() && (mapping.value().evse == evse_index));
});
if (it == evse_managers.end()) {
res.error = ResponseErrorEnum::ErrorInvalidEVSEIndex;
EVLOG_warning << "No EVSE manager found for index: " << evse_index;
return res;
}
const auto& evse_manager = *it;
auto evse_store = data_store.get_evse_store(evse_index);
// If the charging allowed state is already set to the desired value, we can return early.
if (evse_store->evsestatus.get_data()->charging_allowed == charging_allowed) {
EVLOG_debug << "Charging allowed state for EVSE index: " << evse_index
<< " is already set to: " << charging_allowed;
res.error = ResponseErrorEnum::NoError;
return res;
}
// Determine the current state of the EVSE. In case the EVSE is currently charging, we can use
// the resume-pause methods to control the charging process.
const auto evse_state = evse_store->evsestatus.get_state();
const bool is_charging = (evse_state == types::json_rpc_api::EVSEStateEnum::Charging);
const bool is_charging_paused = (evse_state == types::json_rpc_api::EVSEStateEnum::ChargingPausedEVSE ||
evse_state == types::json_rpc_api::EVSEStateEnum::ChargingPausedEV);
bool is_power_limit = !configured_limits.is_current_set;
if (charging_allowed) {
float phy_limit{0.0f};
// first we need to determine which limits to apply. If the limit (current or power) is already set, we will use
// that.
if (configured_limits.evse_limit.has_value()) {
// If current is set, use the configured current limit
phy_limit = configured_limits.evse_limit.value();
} else {
// If no limits are set, use the default values. TODO: It would be better to get the default values from the
// EVSE manager.
phy_limit = 999.9f; // Default maximum current
is_power_limit = false;
}
// If the phases are not set, we assume DC charging. This means there is no need to apply phase limits.
ErrorResObj result = check_active_phases_and_set_limits(evse_index, phy_limit, is_power_limit);
if (result.error != ResponseErrorEnum::NoError) {
EVLOG_warning << "Failed to set external limits for EVSE index: " << evse_index
<< " with error: " << result.error;
res.error = result.error;
return res;
}
if (is_charging_paused) {
if (!evse_manager->call_resume_charging()) {
success = false;
EVLOG_warning << "Failed to resume charging for EVSE index: " << evse_index;
}
}
} else {
if (is_charging) {
// If charging is not allowed, we need to pause the charging process
if (!evse_manager->call_pause_charging()) {
success = false;
EVLOG_warning << "Failed to pause charging for EVSE index: " << evse_index;
}
} else {
// Additionally, in case the EVSE is not charging, we set the limits to 0 to prevent any charging from
// starting
float max_power{0.0f};
ErrorResObj res_limit =
set_external_limit(evse_index, max_power,
std::function<types::energy::ExternalLimits(float)>([is_power_limit](float value) {
return get_external_limits(value, is_power_limit);
}));
if (res_limit.error != ResponseErrorEnum::NoError) {
EVLOG_warning << "Failed to set external limits for EVSE index: " << evse_index
<< " with error: " << res_limit.error;
return res;
}
}
}
// Update the EVSE status in the data store
// TODO: Add a var to the EVSEManager to track if charging is allowed
evse_store->evsestatus.set_charging_allowed(charging_allowed);
if (success) {
res.error = ResponseErrorEnum::NoError;
EVLOG_debug << "Charging " << (charging_allowed ? "allowed" : "not allowed")
<< " for EVSE index: " << evse_index;
} else {
res.error = ResponseErrorEnum::ErrorValuesNotApplied;
EVLOG_warning << "Failed to set charging " << (charging_allowed ? "allowed" : "not allowed")
<< " for EVSE index: " << evse_index;
}
return res;
}
ErrorResObj RpcApiRequestHandler::set_ac_charging(const int32_t evse_index, bool charging_allowed, bool max_current,
std::optional<int> phase_count) {
(void)evse_index;
(void)charging_allowed;
(void)max_current;
(void)phase_count;
ErrorResObj res{};
res.error = ResponseErrorEnum::ErrorValuesNotApplied;
// TODO: Currently not implemented.
return res;
}
// template method to set the external limits depending on the type of value (current, power, or phase count)
template <typename T>
ErrorResObj RpcApiRequestHandler::set_external_limit(const int32_t evse_index, T value,
std::function<types::energy::ExternalLimits(T)> make_limits) {
ErrorResObj res{};
res.error = ResponseErrorEnum::NoError;
bool is_sink_configured;
try {
is_sink_configured = external_energy_limits::is_evse_sink_configured(evse_energy_sink, evse_index);
} catch (const std::runtime_error&) {
is_sink_configured = false;
}
if (is_sink_configured) {
auto& energy_sink = external_energy_limits::get_evse_sink_by_evse_id(evse_energy_sink, evse_index);
try {
const auto external_limits = make_limits(value);
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: " << e.what();
res.error = ResponseErrorEnum::ErrorInvalidParameter;
}
} else {
res.error = ResponseErrorEnum::ErrorInvalidEVSEIndex;
EVLOG_warning << "No EVSE energy sink configured for evse_index: " << evse_index
<< ". This module does therefore not allow control of amps or power limits for this EVSE";
}
return res;
}
ErrorResObj RpcApiRequestHandler::set_ac_charging_current(const int32_t evse_index, float max_current) {
configured_limits.is_current_set = true;
configured_limits.evse_limit = max_current;
ErrorResObj res{};
const auto evse_store = data_store.get_evse_store(evse_index);
// Skipping applying limits if charging is not allowed.
// In this case, the zero limit is already applied to prevent charging. This value should not be overridden.
if (evse_store->evsestatus.get_data()->charging_allowed == false) {
res.error = ResponseErrorEnum::NoError;
return res;
}
res = check_active_phases_and_set_limits(evse_index, max_current, false);
if (res.error != ResponseErrorEnum::NoError) {
return res;
}
// Wait until the limits are applied or timeout occurs
if (evse_store->evsestatus.wait_until_current_limit_applied(max_current, CURRENT_LIMIT_APPLY_TIMEOUT)) {
res.error = ResponseErrorEnum::NoError;
} else {
res.error = ResponseErrorEnum::ErrorValuesNotApplied;
}
return res;
}
ErrorResObj RpcApiRequestHandler::set_ac_charging_phase_count(const int32_t evse_index, int phase_count) {
ErrorResObj res{};
if (configured_limits.evse_limit.has_value()) {
// In case a current or power limit is already set, we need to consider that when applying the phase count
bool is_power = !configured_limits.is_current_set;
res = set_external_limit(
evse_index, configured_limits.evse_limit.value(),
std::function<types::energy::ExternalLimits(float)>([is_power, phase_count](float phy_value) {
return get_external_limits(phy_value, is_power, phase_count);
}));
} else {
// If no current or power limit is set, we can just apply the phase count
res = set_external_limit(evse_index, phase_count,
std::function<types::energy::ExternalLimits(int)>(
[](int value) { return get_external_limits(static_cast<int32_t>(value)); }));
}
return res;
}
ErrorResObj RpcApiRequestHandler::set_dc_charging(const int32_t evse_index, bool charging_allowed, float max_power) {
(void)evse_index;
(void)charging_allowed;
(void)max_power;
ErrorResObj res{};
res.error = ResponseErrorEnum::ErrorValuesNotApplied;
// TODO: Currently not implemented.
return res;
}
ErrorResObj RpcApiRequestHandler::set_dc_charging_power(const int32_t evse_index, float max_power) {
configured_limits.is_current_set = false;
configured_limits.evse_limit = max_power;
ErrorResObj res =
set_external_limit(evse_index, max_power, std::function<types::energy::ExternalLimits(float)>([](float value) {
return get_external_limits(value, true);
}));
return res;
}
ErrorResObj RpcApiRequestHandler::enable_connector(const int32_t evse_index, int connector_id, bool enable,
int priority) {
ErrorResObj res{};
const auto it = std::find_if(evse_managers.begin(), evse_managers.end(), [&evse_index](const auto& manager) {
const auto mapping = manager->get_mapping();
return (mapping.has_value() && (mapping.value().evse == evse_index));
});
if (it == evse_managers.end()) {
res.error = ResponseErrorEnum::ErrorInvalidEVSEIndex;
EVLOG_warning << "No EVSE manager found for index: " << evse_index;
return res;
}
const auto& evse_manager = *it;
types::evse_manager::EnableDisableSource cmd_source;
cmd_source.enable_source = types::evse_manager::Enable_source::LocalAPI;
cmd_source.enable_state =
enable ? types::evse_manager::Enable_state::Enable : types::evse_manager::Enable_state::Disable;
cmd_source.enable_priority = priority;
const bool result_enabled = evse_manager->call_enable_disable(connector_id, cmd_source);
if (result_enabled == enable) {
res.error = ResponseErrorEnum::NoError;
EVLOG_debug << "Connector " << connector_id << " on EVSE index: " << evse_index << " has been "
<< (enable ? "enabled" : "disabled") << " with priority: " << priority;
} else {
res.error = ResponseErrorEnum::ErrorValuesNotApplied;
EVLOG_warning << "Failed to enable/disable connector " << connector_id << " on EVSE index: " << evse_index;
}
return res;
}
types::json_rpc_api::ErrorResObj RpcApiRequestHandler::check_active_phases_and_set_limits(const int32_t evse_index,
const float phy_value,
const bool is_power) {
ErrorResObj res{};
int phases{0};
const auto evse_store = data_store.get_evse_store(evse_index);
if (const auto _data = evse_store->evsestatus.get_data(); _data.has_value()) {
phases = _data->ac_charge_status.has_value() ? _data->ac_charge_status.value().evse_active_phase_count : 0;
}
if (phases == 0) {
res = set_external_limit(evse_index, phy_value,
std::function<types::energy::ExternalLimits(float)>(
[is_power](float phy_value) { return get_external_limits(phy_value, is_power); }));
} else {
res =
set_external_limit(evse_index, phy_value,
std::function<types::energy::ExternalLimits(float)>([is_power, phases](float phy_value) {
return get_external_limits(phy_value, is_power, phases);
}));
}
return res;
}