- 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
384 lines
17 KiB
C++
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;
|
|
}
|