- 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
1164 lines
56 KiB
C++
1164 lines
56 KiB
C++
// SPDX-License-Identifier: Apache-2.0
|
|
// Copyright Pionix GmbH and Contributors to EVerest
|
|
#include "ISO15118_chargerImpl.hpp"
|
|
#include <fmt/core.h>
|
|
#include <fmt/ranges.h>
|
|
|
|
#include "session_logger.hpp"
|
|
#include "utils.hpp"
|
|
|
|
#include <iso15118/io/logging.hpp>
|
|
#include <iso15118/session/logger.hpp>
|
|
|
|
namespace module {
|
|
namespace charger {
|
|
|
|
static constexpr auto WAIT_FOR_SETUP_DONE_MS{std::chrono::milliseconds(200)};
|
|
|
|
std::mutex GEL; // Global EVerest Lock
|
|
|
|
namespace dt = iso15118::message_20::datatypes;
|
|
|
|
namespace {
|
|
|
|
iso15118::config::TlsNegotiationStrategy convert_tls_negotiation_strategy(const std::string& strategy) {
|
|
using Strategy = iso15118::config::TlsNegotiationStrategy;
|
|
if (strategy == "ACCEPT_CLIENT_OFFER") {
|
|
return Strategy::ACCEPT_CLIENT_OFFER;
|
|
}
|
|
if (strategy == "ENFORCE_TLS") {
|
|
return Strategy::ENFORCE_TLS;
|
|
}
|
|
if (strategy == "ENFORCE_NO_TLS") {
|
|
return Strategy::ENFORCE_NO_TLS;
|
|
}
|
|
EVLOG_AND_THROW(Everest::EverestConfigError("Invalid choice for tls_negotiation_strategy: " + strategy));
|
|
// better safe than sorry
|
|
}
|
|
|
|
types::iso15118::DisplayParameters convert_display_parameters(const dt::DisplayParameters& in) {
|
|
return {in.present_soc,
|
|
in.min_soc,
|
|
in.target_soc,
|
|
in.max_soc,
|
|
in.remaining_time_to_min_soc,
|
|
in.remaining_time_to_target_soc,
|
|
in.remaining_time_to_max_soc,
|
|
in.charging_complete,
|
|
convert_from_optional(in.battery_energy_capacity),
|
|
in.inlet_hot};
|
|
}
|
|
|
|
auto fill_mobility_needs_modes_from_config(const module::Conf& module_config) {
|
|
|
|
std::vector<iso15118::d20::ControlMobilityNeedsModes> mobility_needs_modes{};
|
|
|
|
if (module_config.supported_dynamic_mode) {
|
|
mobility_needs_modes.push_back({dt::ControlMode::Dynamic, dt::MobilityNeedsMode::ProvidedByEvcc});
|
|
if (module_config.supported_mobility_needs_mode_provided_by_secc) {
|
|
mobility_needs_modes.push_back({dt::ControlMode::Dynamic, dt::MobilityNeedsMode::ProvidedBySecc});
|
|
}
|
|
}
|
|
|
|
if (module_config.supported_scheduled_mode) {
|
|
mobility_needs_modes.push_back({dt::ControlMode::Scheduled, dt::MobilityNeedsMode::ProvidedByEvcc});
|
|
}
|
|
|
|
if (mobility_needs_modes.empty()) {
|
|
EVLOG_warning << "Control mobility modes are empty! Setting dynamic mode as default!";
|
|
mobility_needs_modes.push_back({dt::ControlMode::Dynamic, dt::MobilityNeedsMode::ProvidedByEvcc});
|
|
}
|
|
|
|
return mobility_needs_modes;
|
|
}
|
|
|
|
types::iso15118::AcEvDynamicModeValues fill_ac_ev_dynamic_control_mode(const dt::Dynamic_AC_CLReqControlMode& mode) {
|
|
types::iso15118::AcEvDynamicModeValues values{};
|
|
|
|
values.departure_time = mode.departure_time;
|
|
values.target_energy_request = dt::from_RationalNumber(mode.target_energy_request);
|
|
values.max_energy_request = dt::from_RationalNumber(mode.max_energy_request);
|
|
values.min_energy_request = dt::from_RationalNumber(mode.min_energy_request);
|
|
|
|
return values;
|
|
}
|
|
|
|
types::iso15118::EnergyTransferMode get_energy_transfer_mode(const dt::ServiceCategory& service_category,
|
|
const std::optional<dt::AcConnector>& ac_connector) {
|
|
using EnergyTransferMode = types::iso15118::EnergyTransferMode;
|
|
|
|
EnergyTransferMode requested_energy_transfer = EnergyTransferMode::AC_single_phase_core;
|
|
|
|
if (service_category == dt::ServiceCategory::AC && ac_connector.has_value()) {
|
|
if (ac_connector.value() == dt::AcConnector::SinglePhase) {
|
|
requested_energy_transfer = EnergyTransferMode::AC_single_phase_core;
|
|
} else if (ac_connector.value() == dt::AcConnector::ThreePhase) {
|
|
requested_energy_transfer = EnergyTransferMode::AC_three_phase_core;
|
|
}
|
|
} else if (service_category == dt::ServiceCategory::AC_BPT) {
|
|
requested_energy_transfer = EnergyTransferMode::AC_BPT;
|
|
} else if (service_category == dt::ServiceCategory::DC) {
|
|
requested_energy_transfer = EnergyTransferMode::DC;
|
|
} else if (service_category == dt::ServiceCategory::DC_ACDP) {
|
|
requested_energy_transfer = EnergyTransferMode::DC_ACDP;
|
|
} else if (service_category == dt::ServiceCategory::DC_BPT) {
|
|
requested_energy_transfer = EnergyTransferMode::DC_BPT;
|
|
} else if (service_category == dt::ServiceCategory::DC_ACDP_BPT) {
|
|
requested_energy_transfer = EnergyTransferMode::DC_ACDP_BPT;
|
|
} else if (service_category == dt::ServiceCategory::MCS) {
|
|
requested_energy_transfer = EnergyTransferMode::MCS;
|
|
} else if (service_category == dt::ServiceCategory::MCS_BPT) {
|
|
requested_energy_transfer = EnergyTransferMode::MCS_BPT;
|
|
}
|
|
|
|
return requested_energy_transfer;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
void ISO15118_chargerImpl::init() {
|
|
|
|
// setup logging routine
|
|
iso15118::io::set_logging_callback([](const iso15118::LogLevel& level, const std::string& msg) {
|
|
switch (level) {
|
|
case iso15118::LogLevel::Error:
|
|
EVLOG_error << msg;
|
|
break;
|
|
case iso15118::LogLevel::Warning:
|
|
EVLOG_warning << msg;
|
|
break;
|
|
case iso15118::LogLevel::Info:
|
|
EVLOG_info << msg;
|
|
break;
|
|
case iso15118::LogLevel::Debug:
|
|
EVLOG_debug << msg;
|
|
break;
|
|
case iso15118::LogLevel::Trace:
|
|
EVLOG_verbose << msg;
|
|
break;
|
|
default:
|
|
EVLOG_critical << "(Loglevel not defined) - " << msg;
|
|
break;
|
|
}
|
|
});
|
|
|
|
supported_vas_services_per_provider.reserve(mod->r_iso15118_vas.size());
|
|
|
|
for (size_t i = 0; i < mod->r_iso15118_vas.size(); i++) {
|
|
supported_vas_services_per_provider.emplace_back();
|
|
|
|
this->mod->r_iso15118_vas[i]->subscribe_offered_vas(
|
|
[this, i](const types::iso15118_vas::OfferedServices& offered_services) {
|
|
std::vector<uint16_t> service_ids;
|
|
std::scoped_lock lock(vas_mutex);
|
|
|
|
service_ids.reserve(offered_services.services.size());
|
|
for (const auto& item : offered_services.services) {
|
|
service_ids.push_back(item.service_id);
|
|
}
|
|
|
|
EVLOG_verbose << fmt::format("Updated Supported VAS services for provider #{} ({} service{})", i,
|
|
offered_services.services.size(),
|
|
offered_services.services.size() > 1 ? "s" : "");
|
|
supported_vas_services_per_provider[i] = service_ids;
|
|
|
|
// report to controller if it exists, also check for duplicate service ids
|
|
update_supported_vas_services();
|
|
});
|
|
}
|
|
}
|
|
|
|
void ISO15118_chargerImpl::ready() {
|
|
publish_supported_app_protocols_secc(
|
|
types::iso15118::SupportedAppProtocols{{types::iso15118::SupportedAppProtocol::ISO15118D20}});
|
|
|
|
while (true) {
|
|
if (setup_steps_done.all()) {
|
|
break;
|
|
}
|
|
std::this_thread::sleep_for(WAIT_FOR_SETUP_DONE_MS);
|
|
}
|
|
|
|
const auto session_logger = std::make_unique<SessionLogger>(mod->config.logging_path);
|
|
|
|
// Obtain certificate location from the security module
|
|
const auto certificate_response = mod->r_security->call_get_leaf_certificate_info(
|
|
types::evse_security::LeafCertificateType::V2G, types::evse_security::EncodingFormat::PEM, false);
|
|
|
|
if (certificate_response.status != types::evse_security::GetCertificateInfoStatus::Accepted or
|
|
!certificate_response.info.has_value()) {
|
|
EVLOG_AND_THROW(Everest::EverestConfigError("V2G certificate not found"));
|
|
}
|
|
|
|
const auto& certificate_info = certificate_response.info.value();
|
|
std::string path_chain;
|
|
|
|
if (certificate_info.certificate.has_value()) {
|
|
path_chain = certificate_info.certificate.value();
|
|
} else if (certificate_info.certificate_single.has_value()) {
|
|
path_chain = certificate_info.certificate_single.value();
|
|
} else {
|
|
EVLOG_AND_THROW(Everest::EverestConfigError("V2G certificate not found"));
|
|
}
|
|
|
|
const auto v2g_root_cert_path = mod->r_security->call_get_verify_file(types::evse_security::CaCertificateType::V2G);
|
|
const auto mo_root_cert_path = mod->r_security->call_get_verify_file(types::evse_security::CaCertificateType::MO);
|
|
|
|
// TODO(mlitre): Should be updated once libiso supports service renegotiation
|
|
this->mod->p_extensions->publish_service_renegotiation_supported(false);
|
|
|
|
const iso15118::TbdConfig tbd_config = {
|
|
{
|
|
iso15118::config::CertificateBackend::EVEREST_LAYOUT,
|
|
{}, ///< config_string
|
|
path_chain, ///< path_certificate_chain
|
|
certificate_info.key, ///< path_certificate_key
|
|
certificate_info.password, ///< private_key_password
|
|
v2g_root_cert_path, ///< path_certificate_v2g_root
|
|
mo_root_cert_path, ///< path_certificate_mo_root
|
|
mod->config.enable_ssl_logging, ///< enable_ssl_logging
|
|
mod->config.enable_tls_key_logging, ///< enable_tls_key_logging
|
|
mod->config.enforce_tls_1_3, ///< enforce_tls_1_3
|
|
mod->config.tls_key_logging_path, ///< tls_key_logging_path
|
|
},
|
|
mod->config.device,
|
|
convert_tls_negotiation_strategy(mod->config.tls_negotiation_strategy),
|
|
mod->config.enable_sdp_server,
|
|
};
|
|
const auto callbacks = create_callbacks();
|
|
|
|
setup_config.control_mobility_modes = fill_mobility_needs_modes_from_config(mod->config);
|
|
|
|
if (not mod->config.custom_protocol_namespace.empty()) {
|
|
setup_config.custom_protocol.emplace(mod->config.custom_protocol_namespace);
|
|
}
|
|
|
|
controller = std::make_unique<iso15118::TbdController>(tbd_config, callbacks, setup_config);
|
|
|
|
// if the vas providers report their supported vas services before the controller exists,
|
|
// we need to update the controller with the supported vas services after instantiation
|
|
{
|
|
std::scoped_lock lock(vas_mutex);
|
|
update_supported_vas_services();
|
|
}
|
|
|
|
try {
|
|
controller->loop();
|
|
} catch (const std::exception& e) {
|
|
EVLOG_error << e.what();
|
|
}
|
|
}
|
|
|
|
void ISO15118_chargerImpl::update_supported_vas_services() {
|
|
iso15118::d20::SupportedVASs supported_vas_services;
|
|
|
|
for (const auto& provider_services : supported_vas_services_per_provider) {
|
|
for (const auto& service_id : provider_services) {
|
|
// Check for duplicate service IDs across all providers
|
|
if (std::find(supported_vas_services.begin(), supported_vas_services.end(), service_id) !=
|
|
supported_vas_services.end()) {
|
|
EVLOG_error << "Duplicate VAS service ID found: " << std::to_string(service_id)
|
|
<< ". Skipping this service.";
|
|
continue;
|
|
}
|
|
|
|
supported_vas_services.push_back(service_id);
|
|
}
|
|
}
|
|
|
|
if (this->controller) {
|
|
EVLOG_verbose << fmt::format("Updated controller VAS list: {}", fmt::join(supported_vas_services, ","));
|
|
this->controller->update_supported_vas_services(supported_vas_services);
|
|
} else {
|
|
EVLOG_verbose << "Controller not initialized, skipping setting supported VAS services.";
|
|
}
|
|
}
|
|
|
|
std::optional<size_t> ISO15118_chargerImpl::get_vas_provider_index(uint16_t service_id) {
|
|
std::scoped_lock lock(vas_mutex);
|
|
|
|
for (size_t i = 0; i < supported_vas_services_per_provider.size(); i++) {
|
|
const auto& provider_services = supported_vas_services_per_provider[i];
|
|
if (std::find(provider_services.begin(), provider_services.end(), service_id) != provider_services.end()) {
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return std::nullopt; // Service ID not found in any provider's list
|
|
}
|
|
|
|
iso15118::session::feedback::Callbacks ISO15118_chargerImpl::create_callbacks() {
|
|
|
|
using ScheduleControlModeDC = dt::Scheduled_DC_CLReqControlMode;
|
|
using BPT_ScheduleReqControlModeDC = dt::BPT_Scheduled_DC_CLReqControlMode;
|
|
using DynamicReqControlModeDC = dt::Dynamic_DC_CLReqControlMode;
|
|
using BPT_DynamicReqControlModeDC = dt::BPT_Dynamic_DC_CLReqControlMode;
|
|
|
|
using ScheduleControlModeAC = dt::Scheduled_AC_CLReqControlMode;
|
|
using BPT_ScheduleReqControlModeAC = dt::BPT_Scheduled_AC_CLReqControlMode;
|
|
using DynamicReqControlModeAC = dt::Dynamic_AC_CLReqControlMode;
|
|
using BPT_DynamicReqControlModeAC = dt::BPT_Dynamic_AC_CLReqControlMode;
|
|
|
|
namespace feedback = iso15118::session::feedback;
|
|
|
|
feedback::Callbacks callbacks;
|
|
|
|
callbacks.dc_pre_charge_target_voltage = [this](float target_voltage) {
|
|
publish_dc_ev_target_voltage_current({target_voltage, 0});
|
|
};
|
|
|
|
callbacks.notify_ev_charging_needs =
|
|
[this](const dt::ServiceCategory& service_category, const std::optional<dt::AcConnector>& ac_connector,
|
|
const dt::ControlMode& control_mode, const dt::MobilityNeedsMode& mobility_needs_mode,
|
|
const feedback::EvseTransferLimits& evse_limits, const feedback::EvTransferLimits& ev_limits,
|
|
const feedback::EvSEControlMode& ev_control_mode,
|
|
const std::vector<dt::ServiceCategory>& ev_energy_services) {
|
|
// Everest types sent to OCPP
|
|
|
|
types::iso15118::ChargingNeeds charging_needs;
|
|
|
|
charging_needs.requested_energy_transfer = get_energy_transfer_mode(service_category, ac_connector);
|
|
if (!ev_energy_services.empty()) {
|
|
charging_needs.available_energy_transfer = std::vector<types::iso15118::EnergyTransferMode>{};
|
|
charging_needs.available_energy_transfer->reserve(ev_energy_services.size());
|
|
for (const auto& energy_transfer : ev_energy_services) {
|
|
charging_needs.available_energy_transfer->emplace_back(
|
|
get_energy_transfer_mode(energy_transfer, std::nullopt));
|
|
}
|
|
}
|
|
|
|
if (control_mode == dt::ControlMode::Scheduled) {
|
|
charging_needs.control_mode = types::iso15118::ControlMode::ScheduledControl;
|
|
} else if (control_mode == dt::ControlMode::Dynamic) {
|
|
charging_needs.control_mode = types::iso15118::ControlMode::DynamicControl;
|
|
} else {
|
|
EVLOG_error << "Invalid value received for control mode! Not sending 'ChargingNeeds'.";
|
|
return;
|
|
}
|
|
|
|
if (mobility_needs_mode == dt::MobilityNeedsMode::ProvidedByEvcc) {
|
|
charging_needs.mobility_needs_mode = types::iso15118::MobilityNeedsMode::EVCC;
|
|
} else if (mobility_needs_mode == dt::MobilityNeedsMode::ProvidedBySecc) {
|
|
charging_needs.mobility_needs_mode = types::iso15118::MobilityNeedsMode::EVCC_SECC;
|
|
} else {
|
|
EVLOG_error << "Invalid value received for mobility needs mode! Not sending 'ChargingNeeds'.";
|
|
return;
|
|
}
|
|
|
|
// For dash20 the data we will publish will be the v2xChargingParameters
|
|
types::iso15118::V2XChargingParameters& v2x_charging_parameters =
|
|
charging_needs.v2x_charging_parameters.emplace();
|
|
|
|
if (const auto* dc_evse_limits = std::get_if<iso15118::d20::DcTransferLimits>(&evse_limits)) {
|
|
if (const auto* dc_ev_limits = std::get_if<dt::DC_CPDReqEnergyTransferMode>(&ev_limits)) {
|
|
fill_v2x_charging_parameters(v2x_charging_parameters, *dc_evse_limits, *dc_ev_limits);
|
|
} else if (const auto* dc_ev_limits = std::get_if<dt::BPT_DC_CPDReqEnergyTransferMode>(&ev_limits)) {
|
|
fill_v2x_charging_parameters(v2x_charging_parameters, *dc_evse_limits, *dc_ev_limits);
|
|
}
|
|
} else if (const auto* ac_evse_limits = std::get_if<iso15118::d20::AcTransferLimits>(&evse_limits)) {
|
|
if (const auto* ac_ev_limits = std::get_if<dt::AC_CPDReqEnergyTransferMode>(&ev_limits)) {
|
|
fill_v2x_charging_parameters(v2x_charging_parameters, *ac_evse_limits, *ac_ev_limits);
|
|
} else if (const auto* ac_ev_limits = std::get_if<dt::BPT_AC_CPDReqEnergyTransferMode>(&ev_limits)) {
|
|
fill_v2x_charging_parameters(v2x_charging_parameters, *ac_evse_limits, *ac_ev_limits);
|
|
}
|
|
} else {
|
|
EVLOG_error << "Invalid type received for EVSE limits! Not sending 'ChargingNeeds'.";
|
|
return;
|
|
}
|
|
|
|
if (const auto* ev_se_control_mode = std::get_if<dt::Scheduled_SEReqControlMode>(&ev_control_mode)) {
|
|
fill_v2x_charging_parameters(v2x_charging_parameters, *ev_se_control_mode);
|
|
} else if (const auto* ev_se_control_mode = std::get_if<dt::Dynamic_SEReqControlMode>(&ev_control_mode)) {
|
|
fill_v2x_charging_parameters(v2x_charging_parameters, *ev_se_control_mode);
|
|
} else {
|
|
EVLOG_error << "Invalid type received for EV Control Mode! Not sending 'ChargingNeeds'.";
|
|
return;
|
|
}
|
|
|
|
// Publish charging needs through the extensions
|
|
this->mod->p_extensions->publish_charging_needs(charging_needs);
|
|
};
|
|
|
|
callbacks.dc_charge_loop_req = [this](const feedback::DcChargeLoopReq& dc_charge_loop_req) {
|
|
if (const auto* dc_control_mode = std::get_if<feedback::DcReqControlMode>(&dc_charge_loop_req)) {
|
|
if (const auto* scheduled_mode = std::get_if<ScheduleControlModeDC>(dc_control_mode)) {
|
|
const auto target_voltage = dt::from_RationalNumber(scheduled_mode->target_voltage);
|
|
const auto target_current = dt::from_RationalNumber(scheduled_mode->target_current);
|
|
|
|
publish_dc_ev_target_voltage_current({target_voltage, target_current});
|
|
|
|
if (scheduled_mode->max_charge_current and scheduled_mode->max_voltage and
|
|
scheduled_mode->max_charge_power) {
|
|
const auto max_current = dt::from_RationalNumber(scheduled_mode->max_charge_current.value());
|
|
const auto max_voltage = dt::from_RationalNumber(scheduled_mode->max_voltage.value());
|
|
const auto max_power = dt::from_RationalNumber(scheduled_mode->max_charge_power.value());
|
|
publish_dc_ev_maximum_limits({max_current, max_power, max_voltage});
|
|
}
|
|
|
|
} else if (const auto* bpt_scheduled_mode = std::get_if<BPT_ScheduleReqControlModeDC>(dc_control_mode)) {
|
|
const auto target_voltage = dt::from_RationalNumber(bpt_scheduled_mode->target_voltage);
|
|
const auto target_current = dt::from_RationalNumber(bpt_scheduled_mode->target_current);
|
|
publish_dc_ev_target_voltage_current({target_voltage, target_current});
|
|
|
|
if (bpt_scheduled_mode->max_charge_current and bpt_scheduled_mode->max_voltage and
|
|
bpt_scheduled_mode->max_charge_power) {
|
|
const auto max_current = dt::from_RationalNumber(bpt_scheduled_mode->max_charge_current.value());
|
|
const auto max_voltage = dt::from_RationalNumber(bpt_scheduled_mode->max_voltage.value());
|
|
const auto max_power = dt::from_RationalNumber(bpt_scheduled_mode->max_charge_power.value());
|
|
publish_dc_ev_maximum_limits({max_current, max_power, max_voltage});
|
|
}
|
|
|
|
// publish_dc_ev_maximum_limits({max_limits.current, max_limits.power, max_limits.voltage});
|
|
} else if (const auto* dynamic_mode = std::get_if<DynamicReqControlModeDC>(dc_control_mode)) {
|
|
publish_d20_dc_dynamic_charge_mode(convert_dynamic_values(*dynamic_mode));
|
|
} else if (const auto* bpt_dynamic_mode = std::get_if<BPT_DynamicReqControlModeDC>(dc_control_mode)) {
|
|
publish_d20_dc_dynamic_charge_mode(convert_dynamic_values(*bpt_dynamic_mode));
|
|
}
|
|
} else if (const auto* display_parameters = std::get_if<dt::DisplayParameters>(&dc_charge_loop_req)) {
|
|
publish_display_parameters(convert_display_parameters(*display_parameters));
|
|
} else if (const auto* present_voltage = std::get_if<feedback::PresentVoltage>(&dc_charge_loop_req)) {
|
|
publish_dc_ev_present_voltage(dt::from_RationalNumber(*present_voltage));
|
|
} else if (const auto* meter_info_requested = std::get_if<feedback::MeterInfoRequested>(&dc_charge_loop_req)) {
|
|
if (*meter_info_requested) {
|
|
EVLOG_info << "Meter info is requested from EV";
|
|
publish_meter_info_requested(nullptr);
|
|
}
|
|
}
|
|
};
|
|
|
|
callbacks.dc_max_limits = [this](const feedback::DcMaximumLimits& max_limits) {
|
|
publish_dc_ev_maximum_limits({max_limits.current, max_limits.power, max_limits.voltage});
|
|
};
|
|
|
|
callbacks.ac_limits = [this](const feedback::AcLimits& limits) {
|
|
if (const auto* ac_transfer_mode = std::get_if<dt::AC_CPDReqEnergyTransferMode>(&limits)) {
|
|
publish_ac_ev_power_limits(fill_ac_ev_power_limits(*ac_transfer_mode));
|
|
} else if (const auto* ac_bpt_transfer_mode = std::get_if<dt::BPT_AC_CPDReqEnergyTransferMode>(&limits)) {
|
|
publish_ac_ev_power_limits(fill_ac_ev_power_limits(*ac_bpt_transfer_mode));
|
|
}
|
|
};
|
|
|
|
callbacks.ac_charge_loop_req = [this](const feedback::AcChargeLoopReq& ac_charge_loop_req) {
|
|
if (const auto* ac_control_mode = std::get_if<feedback::AcReqControlMode>(&ac_charge_loop_req)) {
|
|
if (const auto* scheduled_mode = std::get_if<ScheduleControlModeAC>(ac_control_mode)) {
|
|
if (scheduled_mode->max_charge_power.has_value() or scheduled_mode->max_charge_power_L2.has_value() or
|
|
scheduled_mode->max_charge_power_L3.has_value() or scheduled_mode->min_charge_power.has_value() or
|
|
scheduled_mode->min_charge_power_L2.has_value() or
|
|
scheduled_mode->min_charge_power_L3.has_value()) {
|
|
publish_ac_ev_power_limits(fill_ac_ev_power_limits(*scheduled_mode));
|
|
}
|
|
publish_ac_ev_present_powers(fill_ac_ev_present_power_values(*scheduled_mode));
|
|
// TODO(SL): publish_scheduled_control_mode
|
|
} else if (const auto* bpt_scheduled_mode = std::get_if<BPT_ScheduleReqControlModeAC>(ac_control_mode)) {
|
|
if (bpt_scheduled_mode->max_charge_power.has_value() or
|
|
bpt_scheduled_mode->max_charge_power_L2.has_value() or
|
|
bpt_scheduled_mode->max_charge_power_L3.has_value() or
|
|
bpt_scheduled_mode->min_charge_power.has_value() or
|
|
bpt_scheduled_mode->min_charge_power_L2.has_value() or
|
|
bpt_scheduled_mode->min_charge_power_L3.has_value()) {
|
|
publish_ac_ev_power_limits(fill_ac_ev_power_limits(*bpt_scheduled_mode));
|
|
}
|
|
publish_ac_ev_present_powers(fill_ac_ev_present_power_values(*bpt_scheduled_mode));
|
|
// TODO(SL): publish_scheduled_control_mode
|
|
} else if (const auto* dynamic_mode = std::get_if<DynamicReqControlModeAC>(ac_control_mode)) {
|
|
publish_ac_ev_power_limits(fill_ac_ev_power_limits(*dynamic_mode));
|
|
publish_ac_ev_present_powers(fill_ac_ev_present_power_values(*dynamic_mode));
|
|
publish_ac_ev_dynamic_control_mode(fill_ac_ev_dynamic_control_mode(*dynamic_mode));
|
|
} else if (const auto* bpt_dynamic_mode = std::get_if<BPT_DynamicReqControlModeAC>(ac_control_mode)) {
|
|
publish_ac_ev_power_limits(fill_ac_ev_power_limits(*bpt_dynamic_mode));
|
|
publish_ac_ev_present_powers(fill_ac_ev_present_power_values(*bpt_dynamic_mode));
|
|
auto ev_dynamic_values = fill_ac_ev_dynamic_control_mode(*bpt_dynamic_mode);
|
|
ev_dynamic_values.max_v2x_energy_request =
|
|
convert_from_optional(bpt_dynamic_mode->max_v2x_energy_request);
|
|
ev_dynamic_values.min_v2x_energy_request =
|
|
convert_from_optional(bpt_dynamic_mode->min_v2x_energy_request);
|
|
publish_ac_ev_dynamic_control_mode(ev_dynamic_values);
|
|
}
|
|
} else if (const auto* display_parameters = std::get_if<dt::DisplayParameters>(&ac_charge_loop_req)) {
|
|
publish_display_parameters(convert_display_parameters(*display_parameters));
|
|
} else if (const auto* meter_info_requested = std::get_if<feedback::MeterInfoRequested>(&ac_charge_loop_req)) {
|
|
if (*meter_info_requested) {
|
|
EVLOG_info << "Meter info is requested from EV";
|
|
publish_meter_info_requested(nullptr);
|
|
}
|
|
}
|
|
};
|
|
|
|
callbacks.signal = [this](feedback::Signal signal) {
|
|
using Signal = feedback::Signal;
|
|
switch (signal) {
|
|
case Signal::PRE_CHARGE_STARTED:
|
|
publish_start_pre_charge(nullptr);
|
|
break;
|
|
case Signal::CHARGE_LOOP_STARTED:
|
|
publish_current_demand_started(nullptr);
|
|
break;
|
|
case Signal::SETUP_FINISHED:
|
|
publish_v2g_setup_finished(nullptr);
|
|
break;
|
|
case Signal::START_CABLE_CHECK:
|
|
publish_start_cable_check(nullptr);
|
|
break;
|
|
case Signal::REQUIRE_AUTH_EIM:
|
|
publish_require_auth_eim(nullptr);
|
|
break;
|
|
case Signal::CHARGE_LOOP_FINISHED:
|
|
publish_current_demand_finished(nullptr);
|
|
break;
|
|
case Signal::DC_OPEN_CONTACTOR:
|
|
publish_dc_open_contactor(nullptr);
|
|
break;
|
|
case Signal::AC_CLOSE_CONTACTOR:
|
|
publish_ac_close_contactor(nullptr);
|
|
break;
|
|
case Signal::AC_OPEN_CONTACTOR:
|
|
publish_ac_open_contactor(nullptr);
|
|
break;
|
|
case Signal::DLINK_TERMINATE:
|
|
publish_dlink_terminate(nullptr);
|
|
break;
|
|
case Signal::DLINK_PAUSE:
|
|
publish_dlink_pause(nullptr);
|
|
break;
|
|
case Signal::DLINK_ERROR:
|
|
publish_dlink_error(nullptr);
|
|
break;
|
|
}
|
|
};
|
|
|
|
callbacks.v2g_message = [this](iso15118::message_20::Type id) {
|
|
const auto v2g_message_id = convert_v2g_message_type(id);
|
|
publish_v2g_messages({v2g_message_id});
|
|
};
|
|
|
|
callbacks.evccid = [this](const std::string& evccid) { publish_evcc_id(evccid); };
|
|
|
|
callbacks.selected_protocol = [this](const std::string& protocol) { publish_selected_protocol(protocol); };
|
|
|
|
callbacks.selected_service_parameters = [this](const iso15118::d20::SelectedServiceParameters& parameters) {
|
|
types::iso15118::SelectedServiceParameters selected_parameters{};
|
|
|
|
if (parameters.selected_energy_service == dt::ServiceCategory::AC) {
|
|
selected_parameters.energy_transfer = types::iso15118::ServiceCategory::AC;
|
|
} else if (parameters.selected_energy_service == dt::ServiceCategory::DC) {
|
|
selected_parameters.energy_transfer = types::iso15118::ServiceCategory::DC;
|
|
} else if (parameters.selected_energy_service == dt::ServiceCategory::WPT) {
|
|
selected_parameters.energy_transfer = types::iso15118::ServiceCategory::WPT;
|
|
} else if (parameters.selected_energy_service == dt::ServiceCategory::DC_ACDP) {
|
|
selected_parameters.energy_transfer = types::iso15118::ServiceCategory::DC_ACDP;
|
|
} else if (parameters.selected_energy_service == dt::ServiceCategory::AC_BPT) {
|
|
selected_parameters.energy_transfer = types::iso15118::ServiceCategory::AC_BPT;
|
|
} else if (parameters.selected_energy_service == dt::ServiceCategory::DC_BPT) {
|
|
selected_parameters.energy_transfer = types::iso15118::ServiceCategory::DC_BPT;
|
|
} else if (parameters.selected_energy_service == dt::ServiceCategory::DC_ACDP_BPT) {
|
|
selected_parameters.energy_transfer = types::iso15118::ServiceCategory::DC_ACDP_BPT;
|
|
} else if (parameters.selected_energy_service == dt::ServiceCategory::MCS) {
|
|
selected_parameters.energy_transfer = types::iso15118::ServiceCategory::MCS;
|
|
} else if (parameters.selected_energy_service == dt::ServiceCategory::MCS_BPT) {
|
|
selected_parameters.energy_transfer = types::iso15118::ServiceCategory::MCS_BPT;
|
|
} else {
|
|
EVLOG_critical << "Energy service is apparently no energy service!";
|
|
}
|
|
|
|
if (const auto* ac_connector = std::get_if<dt::AcConnector>(¶meters.selected_connector)) {
|
|
if (*ac_connector == dt::AcConnector::SinglePhase) {
|
|
selected_parameters.connector = types::iso15118::Connector::SinglePhase;
|
|
} else {
|
|
selected_parameters.connector = types::iso15118::Connector::ThreePhase;
|
|
}
|
|
} else if (const auto* dc_connector = std::get_if<dt::DcConnector>(¶meters.selected_connector)) {
|
|
if (*dc_connector == dt::DcConnector::Core) {
|
|
selected_parameters.connector = types::iso15118::Connector::Core;
|
|
} else if (*dc_connector == dt::DcConnector::Extended) {
|
|
selected_parameters.connector = types::iso15118::Connector::Extended;
|
|
} else if (*dc_connector == dt::DcConnector::Dual2) {
|
|
selected_parameters.connector = types::iso15118::Connector::Dual2;
|
|
} else {
|
|
selected_parameters.connector = types::iso15118::Connector::Dual4;
|
|
}
|
|
} else if (const auto* mcs_connector = std::get_if<dt::McsConnector>(¶meters.selected_connector)) {
|
|
if (*mcs_connector == dt::McsConnector::Mcs) {
|
|
selected_parameters.connector = types::iso15118::Connector::Mcs;
|
|
} else if (*mcs_connector == dt::McsConnector::Chaoji) {
|
|
selected_parameters.connector = types::iso15118::Connector::Chaoji;
|
|
} else if (*mcs_connector == dt::McsConnector::UltraChaoji) {
|
|
selected_parameters.connector = types::iso15118::Connector::UltraChaoji;
|
|
} else if (*mcs_connector == dt::McsConnector::rMcs) {
|
|
selected_parameters.connector = types::iso15118::Connector::rMcs;
|
|
} else if (*mcs_connector == dt::McsConnector::xMcs) {
|
|
selected_parameters.connector = types::iso15118::Connector::xMcs;
|
|
} else if (*mcs_connector == dt::McsConnector::Aviation) {
|
|
selected_parameters.connector = types::iso15118::Connector::Aviation;
|
|
} else if (*mcs_connector == dt::McsConnector::Marine) {
|
|
selected_parameters.connector = types::iso15118::Connector::Marine;
|
|
} else {
|
|
selected_parameters.connector = types::iso15118::Connector::Dual4;
|
|
}
|
|
}
|
|
|
|
if (parameters.selected_control_mode == dt::ControlMode::Scheduled) {
|
|
selected_parameters.control_mode = types::iso15118::ControlMode::ScheduledControl;
|
|
} else {
|
|
selected_parameters.control_mode = types::iso15118::ControlMode::DynamicControl;
|
|
}
|
|
if (parameters.selected_mobility_needs_mode == dt::MobilityNeedsMode::ProvidedByEvcc) {
|
|
selected_parameters.mobility_needs_mode = types::iso15118::MobilityNeedsMode::EVCC;
|
|
} else {
|
|
selected_parameters.mobility_needs_mode = types::iso15118::MobilityNeedsMode::EVCC_SECC;
|
|
}
|
|
selected_parameters.pricing = static_cast<types::iso15118::Pricing>(parameters.selected_pricing);
|
|
|
|
if (parameters.selected_bpt_channel.has_value()) {
|
|
if (parameters.selected_bpt_channel.value() == dt::BptChannel::Unified) {
|
|
selected_parameters.bpt_channel = types::iso15118::BptChannel::Unified;
|
|
} else {
|
|
selected_parameters.bpt_channel = types::iso15118::BptChannel::Separated;
|
|
}
|
|
}
|
|
if (parameters.selected_generator_mode.has_value()) {
|
|
if (parameters.selected_generator_mode.value() == dt::GeneratorMode::GridFollowing) {
|
|
selected_parameters.generator_mode = types::iso15118::GeneratorMode::GridFollowing;
|
|
} else {
|
|
selected_parameters.generator_mode = types::iso15118::GeneratorMode::GridForming;
|
|
}
|
|
}
|
|
|
|
publish_selected_service_parameters(selected_parameters);
|
|
};
|
|
|
|
callbacks.selected_vas_services = [this](const dt::VasSelectedServiceList& selected_services) {
|
|
// Group selected services by VAS provider index
|
|
std::map<size_t, std::vector<types::iso15118_vas::SelectedService>> services_by_provider;
|
|
|
|
for (const auto& service : selected_services) {
|
|
const auto provider_index = get_vas_provider_index(service.service_id);
|
|
if (not provider_index.has_value()) {
|
|
EVLOG_warning << "Selected Service ID " << service.service_id
|
|
<< " is not supported by any VAS provider";
|
|
continue;
|
|
}
|
|
|
|
types::iso15118_vas::SelectedService converted_service{service.service_id, service.parameter_set_id};
|
|
services_by_provider[*provider_index].push_back(converted_service);
|
|
}
|
|
|
|
// Notify providers about their selected services
|
|
for (const auto& [provider_index, services] : services_by_provider) {
|
|
mod->r_iso15118_vas[provider_index]->call_selected_services(services);
|
|
}
|
|
};
|
|
|
|
callbacks.get_vas_parameters = [this](uint16_t service_id) -> std::optional<dt::ServiceParameterList> {
|
|
const auto provider_index = get_vas_provider_index(service_id);
|
|
if (not provider_index.has_value()) {
|
|
// note: this can only happen if the service ID was removed from the provider after ServiceDiscoveryRes
|
|
// was sent
|
|
EVLOG_warning << "Service ID " << service_id << " is not supported by any VAS provider";
|
|
return std::nullopt;
|
|
}
|
|
|
|
const auto vas_parameters = mod->r_iso15118_vas[*provider_index]->call_get_service_parameters(service_id);
|
|
|
|
if (vas_parameters.empty()) {
|
|
EVLOG_warning << "No parameters found for service ID " << service_id << " in provider #" << *provider_index;
|
|
return std::nullopt;
|
|
}
|
|
|
|
try {
|
|
return convert_parameter_set_list(vas_parameters);
|
|
} catch (const std::exception& e) {
|
|
EVLOG_error << "Failed to convert VAS parameters for service ID " << service_id << " from provider #"
|
|
<< *provider_index << ": " << e.what();
|
|
return std::nullopt;
|
|
}
|
|
};
|
|
callbacks.ev_information = [this](const iso15118::d20::EVInformation& ev_info) {
|
|
types::iso15118::EvInformation info = convert_ev_info(ev_info);
|
|
this->mod->p_extensions->publish_ev_info(info);
|
|
};
|
|
|
|
callbacks.ev_termination = [this](const std::string& ev_termination_code,
|
|
const std::string& ev_termination_explanation) {
|
|
types::iso15118::EvTermination termination_ctx;
|
|
termination_ctx.ev_termination_code = ev_termination_code;
|
|
termination_ctx.ev_termination_explanation = ev_termination_explanation;
|
|
this->mod->p_charger->publish_ev_termination(termination_ctx);
|
|
};
|
|
|
|
return callbacks;
|
|
}
|
|
|
|
void ISO15118_chargerImpl::handle_setup(types::iso15118::EVSEID& evse_id,
|
|
[[maybe_unused]] types::iso15118::SaeJ2847BidiMode& sae_j2847_mode,
|
|
[[maybe_unused]] bool& debug_mode) {
|
|
|
|
std::scoped_lock lock(GEL);
|
|
setup_config.evse_id = evse_id.evse_id; // TODO(SL): Check format for d20
|
|
|
|
setup_steps_done.set(to_underlying_value(SetupStep::SETUP));
|
|
}
|
|
|
|
void ISO15118_chargerImpl::handle_set_charging_parameters(types::iso15118::SetupPhysicalValues& physical_values) {
|
|
// your code for cmd set_charging_parameters goes here
|
|
}
|
|
|
|
void ISO15118_chargerImpl::handle_session_setup(std::vector<types::iso15118::PaymentOption>& payment_options,
|
|
bool& supported_certificate_service,
|
|
[[maybe_unused]] bool& central_contract_validation_allowed) {
|
|
std::scoped_lock lock(GEL);
|
|
|
|
std::vector<dt::Authorization> auth_services;
|
|
|
|
for (auto& option : payment_options) {
|
|
if (option == types::iso15118::PaymentOption::ExternalPayment) {
|
|
auth_services.push_back(dt::Authorization::EIM);
|
|
} else if (option == types::iso15118::PaymentOption::Contract) {
|
|
// auth_services.push_back(iso15118::message_20::Authorization::PnC);
|
|
EVLOG_warning << "Currently Plug&Charge is not supported and ignored";
|
|
}
|
|
}
|
|
|
|
setup_config.authorization_services = auth_services;
|
|
setup_config.enable_certificate_install_service = supported_certificate_service;
|
|
|
|
setup_steps_done.set(to_underlying_value(SetupStep::AUTH_SETUP));
|
|
}
|
|
|
|
void ISO15118_chargerImpl::handle_bpt_setup(types::iso15118::BptSetup& bpt_config) {
|
|
std::scoped_lock lock(GEL);
|
|
|
|
// Existing values in bpt_setup_config will be overwritten
|
|
auto& bpt_setup = setup_config.bpt_setup_config.emplace();
|
|
|
|
bpt_setup.bpt_channel = bpt_config.bpt_channel == types::iso15118::BptChannel::Unified ? dt::BptChannel::Unified
|
|
: dt::BptChannel::Separated;
|
|
bpt_setup.generator_mode = bpt_config.generator_mode == types::iso15118::GeneratorMode::GridFollowing
|
|
? dt::GeneratorMode::GridFollowing
|
|
: dt::GeneratorMode::GridForming;
|
|
|
|
if (bpt_config.grid_code_detection.has_value()) {
|
|
bpt_setup.grid_code_detection_method =
|
|
bpt_config.grid_code_detection.value() == types::iso15118::GridCodeIslandingDetectionMethod::Active
|
|
? dt::GridCodeIslandingDetectionMethod::Active
|
|
: dt::GridCodeIslandingDetectionMethod::Passive;
|
|
}
|
|
}
|
|
|
|
void ISO15118_chargerImpl::handle_set_powersupply_capabilities(types::power_supply_DC::Capabilities& capabilities) {
|
|
std::scoped_lock lock(GEL);
|
|
|
|
// Export Limits - prefer nominal values over regular values
|
|
const auto max_export_current_A =
|
|
capabilities.nominal_max_export_current_A.value_or(capabilities.max_export_current_A);
|
|
const auto min_export_current_A =
|
|
capabilities.nominal_min_export_current_A.value_or(capabilities.min_export_current_A);
|
|
const auto max_export_power_W = capabilities.nominal_max_export_power_W.value_or(capabilities.max_export_power_W);
|
|
const auto max_export_voltage_V =
|
|
capabilities.nominal_max_export_voltage_V.value_or(capabilities.max_export_voltage_V);
|
|
const auto min_export_voltage_V =
|
|
capabilities.nominal_min_export_voltage_V.value_or(capabilities.min_export_voltage_V);
|
|
|
|
setup_config.powersupply_limits.charge_limits.current.max = dt::from_float(max_export_current_A);
|
|
setup_config.powersupply_limits.charge_limits.current.min = dt::from_float(min_export_current_A);
|
|
setup_config.powersupply_limits.charge_limits.power.max = dt::from_float(max_export_power_W);
|
|
setup_config.powersupply_limits.charge_limits.power.min =
|
|
dt::from_float(min_export_current_A * min_export_voltage_V);
|
|
setup_config.powersupply_limits.voltage.max = dt::from_float(max_export_voltage_V);
|
|
setup_config.powersupply_limits.voltage.min = dt::from_float(min_export_voltage_V);
|
|
|
|
// Discharge Limits - prefer nominal values over regular values
|
|
const auto max_import_power_W = capabilities.nominal_max_import_power_W.has_value()
|
|
? capabilities.nominal_max_import_power_W
|
|
: capabilities.max_import_power_W;
|
|
const auto min_import_current_A = capabilities.nominal_min_import_current_A.has_value()
|
|
? capabilities.nominal_min_import_current_A
|
|
: capabilities.min_import_current_A;
|
|
const auto min_import_voltage_V = capabilities.nominal_min_import_voltage_V.has_value()
|
|
? capabilities.nominal_min_import_voltage_V
|
|
: capabilities.min_import_voltage_V;
|
|
const auto max_import_current_A = capabilities.nominal_max_import_current_A.has_value()
|
|
? capabilities.nominal_max_import_current_A
|
|
: capabilities.max_import_current_A;
|
|
|
|
if (max_import_power_W.has_value() or (min_import_current_A.has_value() and min_import_voltage_V.has_value()) or
|
|
max_import_current_A.has_value() or min_import_current_A.has_value()) {
|
|
auto& discharge_power = (setup_config.powersupply_limits.discharge_limits.has_value())
|
|
? setup_config.powersupply_limits.discharge_limits.value()
|
|
: setup_config.powersupply_limits.discharge_limits.emplace();
|
|
|
|
if (mod->config.negative_bidirectional_limits) {
|
|
discharge_power.power.max = dt::from_float(-std::fabs(max_import_power_W.value_or(0.0)));
|
|
discharge_power.power.min =
|
|
dt::from_float(-std::fabs(min_import_current_A.value_or(0.0)) * min_import_voltage_V.value_or(0.0));
|
|
discharge_power.current.max = dt::from_float(-std::fabs(max_import_current_A.value_or(0.0)));
|
|
discharge_power.current.min = dt::from_float(-std::fabs(min_import_current_A.value_or(0.0)));
|
|
} else {
|
|
discharge_power.power.max = dt::from_float(max_import_power_W.value_or(0.0));
|
|
discharge_power.power.min =
|
|
dt::from_float(min_import_current_A.value_or(0.0) * min_import_voltage_V.value_or(0.0));
|
|
discharge_power.current.max = dt::from_float(max_import_current_A.value_or(0.0));
|
|
discharge_power.current.min = dt::from_float(min_import_current_A.value_or(0.0));
|
|
}
|
|
}
|
|
|
|
if (controller) {
|
|
controller->update_powersupply_limits(setup_config.powersupply_limits);
|
|
}
|
|
|
|
setup_steps_done.set(to_underlying_value(SetupStep::MAX_LIMITS));
|
|
setup_steps_done.set(to_underlying_value(SetupStep::MIN_LIMITS));
|
|
}
|
|
|
|
void ISO15118_chargerImpl::handle_authorization_response(
|
|
types::authorization::AuthorizationStatus& authorization_status,
|
|
[[maybe_unused]] types::authorization::CertificateStatus& certificate_status) {
|
|
|
|
std::scoped_lock lock(GEL);
|
|
// Todo(sl): Currently PnC is not supported
|
|
bool authorized = false;
|
|
|
|
if (authorization_status == types::authorization::AuthorizationStatus::Accepted) {
|
|
authorized = true;
|
|
}
|
|
|
|
if (controller) {
|
|
controller->send_control_event(iso15118::d20::AuthorizationResponse{authorized});
|
|
}
|
|
}
|
|
|
|
void ISO15118_chargerImpl::handle_ac_contactor_closed(bool& status) {
|
|
std::scoped_lock lock(GEL);
|
|
if (controller) {
|
|
controller->send_control_event(iso15118::d20::ClosedContactor{status});
|
|
}
|
|
}
|
|
|
|
void ISO15118_chargerImpl::handle_dlink_ready(bool& value) {
|
|
std::scoped_lock lock(GEL);
|
|
if (controller) {
|
|
controller->set_dlink_ready(value);
|
|
}
|
|
}
|
|
|
|
void ISO15118_chargerImpl::handle_cable_check_finished(bool& status) {
|
|
|
|
std::scoped_lock lock(GEL);
|
|
if (controller) {
|
|
controller->send_control_event(iso15118::d20::CableCheckFinished{status});
|
|
}
|
|
}
|
|
|
|
void ISO15118_chargerImpl::handle_receipt_is_required(bool& receipt_required) {
|
|
// your code for cmd receipt_is_required goes here
|
|
}
|
|
|
|
void ISO15118_chargerImpl::handle_stop_charging(bool& stop) {
|
|
|
|
std::scoped_lock lock(GEL);
|
|
if (controller) {
|
|
controller->send_control_event(iso15118::d20::StopCharging{stop});
|
|
}
|
|
}
|
|
|
|
void ISO15118_chargerImpl::handle_pause_charging(bool& pause) {
|
|
std::scoped_lock lock(GEL);
|
|
if (controller) {
|
|
controller->send_control_event(iso15118::d20::PauseCharging{pause});
|
|
}
|
|
}
|
|
|
|
void ISO15118_chargerImpl::handle_no_energy_pause_charging(types::iso15118::NoEnergyPauseMode& mode) {
|
|
// your code for cmd no_energy_pause_charging goes here
|
|
}
|
|
|
|
bool ISO15118_chargerImpl::handle_update_supported_app_protocols(
|
|
types::iso15118::SupportedAppProtocols& supported_app_protocols) {
|
|
if (supported_app_protocols.app_protocols.empty()) {
|
|
EVLOG_warning << "No supported app protocols configured";
|
|
return false;
|
|
}
|
|
|
|
std::string configured_protocols;
|
|
|
|
for (const auto& protocol : supported_app_protocols.app_protocols) {
|
|
if (!configured_protocols.empty()) {
|
|
configured_protocols += ", ";
|
|
}
|
|
configured_protocols += types::iso15118::supported_app_protocol_to_string(protocol);
|
|
}
|
|
|
|
EVLOG_info << "Configured charging protocols: [" << configured_protocols << "]";
|
|
|
|
bool has_iso15118_d20{false};
|
|
bool all_supported{true};
|
|
|
|
for (const auto& protocol : supported_app_protocols.app_protocols) {
|
|
if (protocol == types::iso15118::SupportedAppProtocol::ISO15118D20) {
|
|
has_iso15118_d20 = true;
|
|
continue;
|
|
} else {
|
|
EVLOG_warning << fmt::format("Unsupported app protocol: {}",
|
|
types::iso15118::supported_app_protocol_to_string(protocol));
|
|
all_supported = false;
|
|
}
|
|
}
|
|
|
|
return all_supported;
|
|
}
|
|
|
|
void ISO15118_chargerImpl::handle_update_energy_transfer_modes(
|
|
std::vector<types::iso15118::EnergyTransferMode>& supported_energy_transfer_modes) {
|
|
|
|
std::scoped_lock lock(GEL);
|
|
|
|
std::vector<dt::ServiceCategory> services;
|
|
|
|
for (const auto& mode : supported_energy_transfer_modes) {
|
|
switch (mode) {
|
|
case types::iso15118::EnergyTransferMode::AC_single_phase_core:
|
|
case types::iso15118::EnergyTransferMode::AC_two_phase:
|
|
case types::iso15118::EnergyTransferMode::AC_three_phase_core:
|
|
services.push_back(dt::ServiceCategory::AC);
|
|
break;
|
|
case types::iso15118::EnergyTransferMode::AC_BPT:
|
|
case types::iso15118::EnergyTransferMode::AC_BPT_DER:
|
|
services.push_back(dt::ServiceCategory::AC_BPT);
|
|
break;
|
|
case types::iso15118::EnergyTransferMode::AC_DER:
|
|
services.push_back(dt::ServiceCategory::AC_DER);
|
|
break;
|
|
case types::iso15118::EnergyTransferMode::DC:
|
|
case types::iso15118::EnergyTransferMode::DC_core:
|
|
case types::iso15118::EnergyTransferMode::DC_extended:
|
|
case types::iso15118::EnergyTransferMode::DC_combo_core:
|
|
case types::iso15118::EnergyTransferMode::DC_unique:
|
|
services.push_back(dt::ServiceCategory::DC);
|
|
break;
|
|
case types::iso15118::EnergyTransferMode::DC_BPT:
|
|
services.push_back(dt::ServiceCategory::DC_BPT);
|
|
break;
|
|
case types::iso15118::EnergyTransferMode::DC_ACDP:
|
|
services.push_back(dt::ServiceCategory::DC_ACDP);
|
|
break;
|
|
case types::iso15118::EnergyTransferMode::DC_ACDP_BPT:
|
|
services.push_back(dt::ServiceCategory::DC_ACDP_BPT);
|
|
break;
|
|
case types::iso15118::EnergyTransferMode::WPT:
|
|
services.push_back(dt::ServiceCategory::WPT);
|
|
break;
|
|
case types::iso15118::EnergyTransferMode::MCS:
|
|
services.push_back(dt::ServiceCategory::MCS);
|
|
break;
|
|
case types::iso15118::EnergyTransferMode::MCS_BPT:
|
|
services.push_back(dt::ServiceCategory::MCS_BPT);
|
|
break;
|
|
}
|
|
}
|
|
|
|
setup_config.supported_energy_services = services;
|
|
|
|
if (controller) {
|
|
controller->update_energy_modes(services);
|
|
}
|
|
|
|
setup_steps_done.set(to_underlying_value(SetupStep::ENERGY_SERVICE));
|
|
}
|
|
|
|
void ISO15118_chargerImpl::handle_update_ac_max_current(double& max_current) {
|
|
// your code for cmd update_ac_max_current goes here
|
|
}
|
|
|
|
void ISO15118_chargerImpl::handle_update_ac_parameters(types::iso15118::AcParameters& ac_parameters) {
|
|
std::scoped_lock lock(GEL);
|
|
|
|
setup_config.ac_limits.nominal_frequency = dt::from_float(ac_parameters.nominal_frequency);
|
|
setup_config.ac_limits.max_power_asymmetry = convert_from_optional(ac_parameters.max_power_asymmetry);
|
|
setup_config.ac_limits.power_ramp_limitation = convert_from_optional(ac_parameters.power_ramp_limitation);
|
|
|
|
// Exisiting values in ac_setup_config will be overwritten
|
|
auto& ac_setup_config = setup_config.ac_setup_config.emplace();
|
|
ac_setup_config.voltage = static_cast<uint32_t>(ac_parameters.nominal_voltage);
|
|
for (const auto& connector : ac_parameters.connectors) {
|
|
if (connector == types::iso15118::Connector::SinglePhase) {
|
|
ac_setup_config.connectors.emplace_back(dt::AcConnector::SinglePhase);
|
|
} else if (connector == types::iso15118::Connector::ThreePhase) {
|
|
ac_setup_config.connectors.emplace_back(dt::AcConnector::ThreePhase);
|
|
}
|
|
}
|
|
|
|
if (controller) {
|
|
controller->update_ac_limits(setup_config.ac_limits);
|
|
}
|
|
}
|
|
|
|
void ISO15118_chargerImpl::handle_update_ac_maximum_limits(types::iso15118::AcEvseMaximumPower& maximum_limits) {
|
|
std::scoped_lock lock(GEL);
|
|
|
|
// NOTE(SL): Only the total values are used here right now. The forwarding of the individual L1, L2 and L3 values
|
|
// will come later.
|
|
setup_config.ac_limits.charge_power.max = dt::from_float(maximum_limits.charge_power.total);
|
|
|
|
if (maximum_limits.discharge_power.has_value()) {
|
|
auto& discharge_power = (setup_config.ac_limits.discharge_power.has_value())
|
|
? setup_config.ac_limits.discharge_power.value()
|
|
: setup_config.ac_limits.discharge_power.emplace();
|
|
discharge_power.max = dt::from_float((mod->config.negative_bidirectional_limits)
|
|
? -std::fabs(maximum_limits.discharge_power.value().total)
|
|
: maximum_limits.discharge_power.value().total);
|
|
}
|
|
|
|
if (controller) {
|
|
controller->update_ac_limits(setup_config.ac_limits);
|
|
}
|
|
|
|
setup_steps_done.set(to_underlying_value(SetupStep::MAX_LIMITS));
|
|
}
|
|
|
|
void ISO15118_chargerImpl::handle_update_ac_minimum_limits(types::iso15118::AcEvseMinimumPower& minimum_limits) {
|
|
std::scoped_lock lock(GEL);
|
|
|
|
// NOTE(SL): Only the total values are used here right now. The forwarding of the individual L1, L2 and L3 values
|
|
// will come later.
|
|
setup_config.ac_limits.charge_power.min = dt::from_float(minimum_limits.charge_power.total);
|
|
|
|
if (minimum_limits.discharge_power.has_value()) {
|
|
auto& discharge_power = (setup_config.ac_limits.discharge_power.has_value())
|
|
? setup_config.ac_limits.discharge_power.value()
|
|
: setup_config.ac_limits.discharge_power.emplace();
|
|
discharge_power.max = dt::from_float((mod->config.negative_bidirectional_limits)
|
|
? -std::fabs(minimum_limits.discharge_power.value().total)
|
|
: minimum_limits.discharge_power.value().total);
|
|
}
|
|
|
|
if (controller) {
|
|
controller->update_ac_limits(setup_config.ac_limits);
|
|
}
|
|
|
|
setup_steps_done.set(to_underlying_value(SetupStep::MIN_LIMITS));
|
|
}
|
|
|
|
void ISO15118_chargerImpl::handle_update_ac_target_values(types::iso15118::AcTargetValues& target_values) {
|
|
std::scoped_lock lock(GEL);
|
|
|
|
if (controller) {
|
|
iso15118::d20::AcTargetPower target_power;
|
|
|
|
// NOTE(SL): How to add L2 and L3 values?
|
|
target_power.target_active_power = dt::from_float(target_values.target_active_power.total);
|
|
if (target_values.target_reactive_power.has_value()) {
|
|
target_power.target_reactive_power = dt::from_float(target_values.target_reactive_power.value().total);
|
|
}
|
|
target_power.target_frequency = convert_from_optional(target_values.target_frequency);
|
|
controller->send_control_event(target_power);
|
|
}
|
|
}
|
|
|
|
void ISO15118_chargerImpl::handle_update_ac_present_power(types::units::Power& present_power) {
|
|
std::scoped_lock lock(GEL);
|
|
|
|
if (controller) {
|
|
iso15118::d20::AcPresentPower ac_present_power;
|
|
// NOTE(SL): Only the total values are used here right now. The forwarding of the individual L1, L2 and L3
|
|
// values will come later.
|
|
ac_present_power.present_active_power.emplace(dt::from_float(present_power.total));
|
|
controller->send_control_event(ac_present_power);
|
|
}
|
|
}
|
|
|
|
void ISO15118_chargerImpl::handle_update_dc_maximum_limits(types::iso15118::DcEvseMaximumLimits& maximum_limits) {
|
|
|
|
std::scoped_lock lock(GEL);
|
|
setup_config.dc_limits.charge_limits.current.max = dt::from_float(maximum_limits.evse_maximum_current_limit);
|
|
setup_config.dc_limits.charge_limits.power.max = dt::from_float(maximum_limits.evse_maximum_power_limit);
|
|
setup_config.dc_limits.voltage.max = dt::from_float(maximum_limits.evse_maximum_voltage_limit);
|
|
|
|
if (maximum_limits.evse_maximum_discharge_current_limit.has_value() or
|
|
maximum_limits.evse_maximum_discharge_power_limit.has_value()) {
|
|
auto& discharge_limits = (setup_config.dc_limits.discharge_limits.has_value())
|
|
? setup_config.dc_limits.discharge_limits.value()
|
|
: setup_config.dc_limits.discharge_limits.emplace();
|
|
if (maximum_limits.evse_maximum_discharge_current_limit.has_value()) {
|
|
discharge_limits.current.max =
|
|
dt::from_float((mod->config.negative_bidirectional_limits)
|
|
? -std::fabs(maximum_limits.evse_maximum_discharge_current_limit.value())
|
|
: maximum_limits.evse_maximum_discharge_current_limit.value());
|
|
}
|
|
|
|
if (maximum_limits.evse_maximum_discharge_power_limit.has_value()) {
|
|
discharge_limits.power.max =
|
|
dt::from_float((mod->config.negative_bidirectional_limits)
|
|
? -std::fabs(maximum_limits.evse_maximum_discharge_power_limit.value())
|
|
: maximum_limits.evse_maximum_discharge_power_limit.value());
|
|
}
|
|
}
|
|
|
|
if (controller) {
|
|
controller->update_dc_limits(setup_config.dc_limits);
|
|
}
|
|
}
|
|
|
|
void ISO15118_chargerImpl::handle_update_dc_minimum_limits(types::iso15118::DcEvseMinimumLimits& minimum_limits) {
|
|
|
|
std::scoped_lock lock(GEL);
|
|
setup_config.dc_limits.charge_limits.current.min = dt::from_float(minimum_limits.evse_minimum_current_limit);
|
|
|
|
setup_config.dc_limits.charge_limits.power.min = dt::from_float(minimum_limits.evse_minimum_power_limit);
|
|
setup_config.dc_limits.voltage.min = dt::from_float(minimum_limits.evse_minimum_voltage_limit);
|
|
|
|
if (minimum_limits.evse_minimum_discharge_current_limit.has_value() or
|
|
minimum_limits.evse_minimum_discharge_power_limit.has_value()) {
|
|
|
|
auto& discharge_limits = (setup_config.dc_limits.discharge_limits.has_value())
|
|
? setup_config.dc_limits.discharge_limits.value()
|
|
: setup_config.dc_limits.discharge_limits.emplace();
|
|
if (minimum_limits.evse_minimum_discharge_current_limit.has_value()) {
|
|
discharge_limits.current.min =
|
|
dt::from_float((mod->config.negative_bidirectional_limits)
|
|
? -std::fabs(minimum_limits.evse_minimum_discharge_current_limit.value())
|
|
: minimum_limits.evse_minimum_discharge_current_limit.value());
|
|
}
|
|
|
|
if (minimum_limits.evse_minimum_discharge_power_limit.has_value()) {
|
|
discharge_limits.power.min =
|
|
dt::from_float((mod->config.negative_bidirectional_limits)
|
|
? -std::fabs(minimum_limits.evse_minimum_discharge_power_limit.value())
|
|
: minimum_limits.evse_minimum_discharge_power_limit.value());
|
|
}
|
|
}
|
|
|
|
if (controller) {
|
|
controller->update_dc_limits(setup_config.dc_limits);
|
|
}
|
|
}
|
|
|
|
void ISO15118_chargerImpl::handle_update_isolation_status(types::iso15118::IsolationStatus& isolation_status) {
|
|
// your code for cmd update_isolation_status goes here
|
|
}
|
|
|
|
void ISO15118_chargerImpl::handle_update_dc_present_values(
|
|
types::iso15118::DcEvsePresentVoltageCurrent& present_voltage_current) {
|
|
|
|
float voltage = present_voltage_current.evse_present_voltage;
|
|
float current = present_voltage_current.evse_present_current.value_or(0);
|
|
|
|
std::scoped_lock lock(GEL);
|
|
if (controller) {
|
|
controller->send_control_event(iso15118::d20::PresentVoltageCurrent{voltage, current});
|
|
}
|
|
}
|
|
|
|
void ISO15118_chargerImpl::handle_update_meter_info(types::powermeter::Powermeter& powermeter) {
|
|
// your code for cmd update_meter_info goes here
|
|
}
|
|
|
|
void ISO15118_chargerImpl::handle_send_error(types::iso15118::EvseError& error) {
|
|
// your code for cmd send_error goes here
|
|
}
|
|
|
|
void ISO15118_chargerImpl::handle_reset_error() {
|
|
// your code for cmd reset_error goes here
|
|
}
|
|
|
|
} // namespace charger
|
|
} // namespace module
|