- 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
653 lines
30 KiB
C++
653 lines
30 KiB
C++
// SPDX-License-Identifier: Apache-2.0
|
|
// Copyright (C) 2022-2023 chargebyte GmbH
|
|
// Copyright (C) 2022-2023 Contributors to EVerest
|
|
#include "ISO15118_chargerImpl.hpp"
|
|
#include "log.hpp"
|
|
#include "sdp.hpp"
|
|
#include "tools.hpp"
|
|
#include "v2g_ctx.hpp"
|
|
#include <algorithm>
|
|
#include <string.h>
|
|
#include <string_view>
|
|
|
|
const std::string CERTS_SUB_DIR = "certs"; // relativ path of the certs
|
|
|
|
using namespace std::chrono_literals;
|
|
using BidiMode = types::iso15118::SaeJ2847BidiMode;
|
|
|
|
namespace module {
|
|
namespace charger {
|
|
|
|
void ISO15118_chargerImpl::init() {
|
|
if (!v2g_ctx) {
|
|
dlog(DLOG_LEVEL_ERROR, "v2g_ctx not created");
|
|
return;
|
|
}
|
|
|
|
/* Configure if_name and auth_mode */
|
|
v2g_ctx->if_name = mod->config.device.data();
|
|
dlog(DLOG_LEVEL_DEBUG, "if_name %s", v2g_ctx->if_name);
|
|
|
|
/* Configure hlc_protocols */
|
|
if (mod->config.supported_DIN70121 == true) {
|
|
v2g_ctx->supported_protocols |= (1 << V2G_PROTO_DIN70121);
|
|
supp_app_protocols_secc.app_protocols.push_back(types::iso15118::SupportedAppProtocol::DIN70121);
|
|
}
|
|
if (mod->config.supported_ISO15118_2 == true) {
|
|
v2g_ctx->supported_protocols |= (1 << V2G_PROTO_ISO15118_2013);
|
|
supp_app_protocols_secc.app_protocols.push_back(types::iso15118::SupportedAppProtocol::ISO15118D2);
|
|
}
|
|
|
|
/* Configure tls_security */
|
|
if (mod->config.tls_security == "force") {
|
|
v2g_ctx->tls_security = TLS_SECURITY_FORCE;
|
|
dlog(DLOG_LEVEL_DEBUG, "tls_security force");
|
|
} else if (mod->config.tls_security == "allow") {
|
|
v2g_ctx->tls_security = TLS_SECURITY_ALLOW;
|
|
dlog(DLOG_LEVEL_DEBUG, "tls_security allow");
|
|
} else {
|
|
v2g_ctx->tls_security = TLS_SECURITY_PROHIBIT;
|
|
dlog(DLOG_LEVEL_DEBUG, "tls_security prohibit");
|
|
}
|
|
|
|
v2g_ctx->terminate_connection_on_failed_response = mod->config.terminate_connection_on_failed_response;
|
|
|
|
v2g_ctx->tls_key_logging = mod->config.tls_key_logging;
|
|
v2g_ctx->tls_key_logging_path = mod->config.tls_key_logging_path;
|
|
|
|
if (mod->config.tls_key_logging == true) {
|
|
dlog(DLOG_LEVEL_DEBUG, "tls-key-logging enabled (path: %s)", mod->config.tls_key_logging_path.c_str());
|
|
}
|
|
|
|
v2g_ctx->network_read_timeout_tls = mod->config.tls_timeout;
|
|
|
|
/* Configure if the contract certificate chain should be verified locally */
|
|
v2g_ctx->session.verify_contract_cert_chain = mod->config.verify_contract_cert_chain;
|
|
|
|
v2g_ctx->session.auth_timeout_eim = mod->config.auth_timeout_eim;
|
|
v2g_ctx->session.auth_timeout_pnc = mod->config.auth_timeout_pnc;
|
|
|
|
v2g_ctx->supported_vas_services_per_provider.reserve(mod->r_iso15118_vas.size());
|
|
|
|
for (size_t i = 0; i < mod->r_iso15118_vas.size(); i++) {
|
|
auto& supported_vas_services = v2g_ctx->supported_vas_services_per_provider.emplace_back();
|
|
|
|
this->mod->r_iso15118_vas.at(i)->subscribe_offered_vas(
|
|
[&supported_vas_services](const types::iso15118_vas::OfferedServices& offered_services) {
|
|
for (auto service : offered_services.services) {
|
|
|
|
const auto id = static_cast<uint16_t>(service.service_id);
|
|
if (id == V2G_SERVICE_ID_CHARGING) {
|
|
continue;
|
|
}
|
|
supported_vas_services.push_back(id);
|
|
|
|
iso2_ServiceType vas_service{};
|
|
init_iso2_ServiceType(&vas_service);
|
|
vas_service.ServiceID = id;
|
|
if (service.service_name.has_value()) {
|
|
strncpy_to_v2g(vas_service.ServiceName.characters, sizeof(vas_service.ServiceName.characters),
|
|
&vas_service.ServiceName.charactersLen, service.service_name.value());
|
|
vas_service.ServiceName_isUsed = 1;
|
|
}
|
|
if (service.service_scope.has_value()) {
|
|
strncpy_to_v2g(vas_service.ServiceScope.characters, sizeof(vas_service.ServiceScope.characters),
|
|
&vas_service.ServiceScope.charactersLen, service.service_scope.value());
|
|
vas_service.ServiceScope_isUsed = 1;
|
|
}
|
|
vas_service.FreeService = service.free_service.value_or(true);
|
|
|
|
if (id == V2G_SERVICE_ID_CERTIFICATE) {
|
|
vas_service.ServiceCategory = iso2_serviceCategoryType_ContractCertificate;
|
|
} else if (id == V2G_SERVICE_ID_INTERNET) {
|
|
vas_service.ServiceCategory = iso2_serviceCategoryType_Internet;
|
|
strncpy_to_v2g(vas_service.ServiceName.characters, sizeof(vas_service.ServiceName.characters),
|
|
&vas_service.ServiceName.charactersLen, "InternetAccess");
|
|
vas_service.ServiceName_isUsed = true;
|
|
} else {
|
|
vas_service.ServiceCategory = iso2_serviceCategoryType_OtherCustom;
|
|
}
|
|
|
|
if (not add_service_to_service_list(v2g_ctx, vas_service)) {
|
|
break;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
void ISO15118_chargerImpl::ready() {
|
|
publish_supported_app_protocols_secc(supp_app_protocols_secc);
|
|
}
|
|
|
|
void ISO15118_chargerImpl::handle_setup(types::iso15118::EVSEID& evse_id,
|
|
types::iso15118::SaeJ2847BidiMode& sae_j2847_mode, bool& debug_mode) {
|
|
|
|
uint8_t len = evse_id.evse_id.length();
|
|
if (len < iso2_EVSEID_CHARACTER_SIZE) {
|
|
memcpy(v2g_ctx->evse_v2g_data.evse_id.bytes, reinterpret_cast<uint8_t*>(evse_id.evse_id.data()), len);
|
|
v2g_ctx->evse_v2g_data.evse_id.bytesLen = len;
|
|
} else {
|
|
dlog(DLOG_LEVEL_WARNING, "EVSEID_CHARACTER_SIZE exceeded (received: %u, max: %u)", len,
|
|
iso2_EVSEID_CHARACTER_SIZE);
|
|
}
|
|
|
|
v2g_ctx->debugMode = debug_mode;
|
|
|
|
if (sae_j2847_mode == BidiMode::V2H || sae_j2847_mode == BidiMode::V2G) {
|
|
struct iso2_ServiceType sae_service;
|
|
|
|
init_iso2_ServiceType(&sae_service);
|
|
|
|
sae_service.FreeService = 1;
|
|
sae_service.ServiceCategory = iso2_serviceCategoryType_OtherCustom;
|
|
|
|
if (sae_j2847_mode == BidiMode::V2H) {
|
|
sae_service.ServiceID = 28472;
|
|
strncpy_to_v2g(sae_service.ServiceName.characters, sizeof(sae_service.ServiceName.characters),
|
|
&sae_service.ServiceName.charactersLen, "SAE J2847/2 V2H");
|
|
sae_service.ServiceName_isUsed = true;
|
|
} else {
|
|
sae_service.ServiceID = 28473;
|
|
strncpy_to_v2g(sae_service.ServiceName.characters, sizeof(sae_service.ServiceName.characters),
|
|
&sae_service.ServiceName.charactersLen, "SAE J2847/2 V2G");
|
|
sae_service.ServiceName_isUsed = true;
|
|
}
|
|
|
|
add_service_to_service_list(v2g_ctx, sae_service);
|
|
}
|
|
}
|
|
|
|
void ISO15118_chargerImpl::handle_set_charging_parameters(types::iso15118::SetupPhysicalValues& physical_values) {
|
|
if (physical_values.ac_nominal_voltage.has_value()) {
|
|
populate_physical_value_float(&v2g_ctx->evse_v2g_data.evse_nominal_voltage,
|
|
physical_values.ac_nominal_voltage.value(), 1, iso2_unitSymbolType_V);
|
|
v2g_ctx->basic_config.evse_ac_nominal_voltage = physical_values.ac_nominal_voltage.value();
|
|
}
|
|
|
|
if (physical_values.dc_current_regulation_tolerance.has_value()) {
|
|
populate_physical_value_float(&v2g_ctx->evse_v2g_data.evse_current_regulation_tolerance,
|
|
physical_values.dc_current_regulation_tolerance.value(), 1,
|
|
iso2_unitSymbolType_A);
|
|
v2g_ctx->evse_v2g_data.evse_current_regulation_tolerance_is_used = 1;
|
|
}
|
|
|
|
if (physical_values.dc_peak_current_ripple.has_value()) {
|
|
populate_physical_value_float(&v2g_ctx->evse_v2g_data.evse_peak_current_ripple,
|
|
physical_values.dc_peak_current_ripple.value(), 1, iso2_unitSymbolType_A);
|
|
}
|
|
|
|
if (physical_values.dc_energy_to_be_delivered.has_value()) {
|
|
populate_physical_value(&v2g_ctx->evse_v2g_data.evse_energy_to_be_delivered,
|
|
physical_values.dc_energy_to_be_delivered.value(), iso2_unitSymbolType_Wh);
|
|
v2g_ctx->evse_v2g_data.evse_energy_to_be_delivered_is_used = 1;
|
|
}
|
|
}
|
|
|
|
void ISO15118_chargerImpl::handle_session_setup(std::vector<types::iso15118::PaymentOption>& payment_options,
|
|
bool& supported_certificate_service,
|
|
bool& central_contract_validation_allowed) {
|
|
if (not v2g_ctx->hlc_pause_active) {
|
|
v2g_ctx->evse_v2g_data.payment_option_list.clear();
|
|
if (not payment_options.empty()) {
|
|
const auto max_payment_options =
|
|
std::min(static_cast<size_t>(iso2_paymentOptionType_2_ARRAY_SIZE), payment_options.size());
|
|
for (size_t i = 0; i < max_payment_options; i++) {
|
|
const auto& payment_option = payment_options.at(i);
|
|
if (payment_option == types::iso15118::PaymentOption::ExternalPayment) {
|
|
v2g_ctx->evse_v2g_data.payment_option_list.push_back(iso2_paymentOptionType_ExternalPayment);
|
|
} else if (payment_option == types::iso15118::PaymentOption::Contract) {
|
|
v2g_ctx->evse_v2g_data.payment_option_list.push_back(iso2_paymentOptionType_Contract);
|
|
} else {
|
|
dlog(DLOG_LEVEL_WARNING, "Unable to configure PaymentOption %s",
|
|
types::iso15118::payment_option_to_string(payment_option).c_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
if (payment_options.empty() or v2g_ctx->evse_v2g_data.payment_option_list.empty()) {
|
|
dlog(DLOG_LEVEL_ERROR, "No valid PaymentOptions configured, falling back to ExternalPayment");
|
|
v2g_ctx->evse_v2g_data.payment_option_list.clear();
|
|
v2g_ctx->evse_v2g_data.payment_option_list.push_back(iso2_paymentOptionType_ExternalPayment);
|
|
}
|
|
}
|
|
|
|
const auto pnc_enabled =
|
|
std::find(v2g_ctx->evse_v2g_data.payment_option_list.begin(), v2g_ctx->evse_v2g_data.payment_option_list.end(),
|
|
iso2_paymentOptionType_Contract) != v2g_ctx->evse_v2g_data.payment_option_list.end();
|
|
|
|
using state_t = tls::Server::state_t;
|
|
const auto tls_server_state = v2g_ctx->tls_server->state();
|
|
|
|
const auto tls_server_available =
|
|
(tls_server_state == state_t::init_complete or tls_server_state == state_t::running);
|
|
|
|
if (pnc_enabled and supported_certificate_service and tls_server_available) {
|
|
// For setting "Certificate" in ServiceList in ServiceDiscoveryRes
|
|
struct iso2_ServiceType cert_service;
|
|
|
|
init_iso2_ServiceType(&cert_service);
|
|
|
|
const int16_t cert_parameter_set_id[] = {1}; // parameter-set-ID 1: "Installation" service. TODO: Support of the
|
|
// "Update" service (parameter-set-ID 2)
|
|
|
|
cert_service.FreeService = 1; // true
|
|
cert_service.ServiceID = 2; // as defined in ISO 15118-2
|
|
cert_service.ServiceCategory = iso2_serviceCategoryType_ContractCertificate;
|
|
strncpy_to_v2g(cert_service.ServiceName.characters, sizeof(cert_service.ServiceName.characters),
|
|
&cert_service.ServiceName.charactersLen, "Certificate");
|
|
cert_service.ServiceName_isUsed = true;
|
|
|
|
add_service_to_service_list(v2g_ctx, cert_service, cert_parameter_set_id,
|
|
sizeof(cert_parameter_set_id) / sizeof(cert_parameter_set_id[0]));
|
|
} else {
|
|
// Make sure Certificate service is not in ServiceList when pnc is not possible
|
|
remove_service_from_service_list_if_exists(v2g_ctx, V2G_SERVICE_ID_CERTIFICATE);
|
|
}
|
|
|
|
// Reset authorization that may have been provided after the TCP was closed, potentially causing
|
|
// authorization to be reused in the next session. Resetting it here prevents that.
|
|
v2g_ctx->evse_v2g_data.evse_processing[PHASE_AUTH] = (uint8_t)iso2_EVSEProcessingType_Ongoing;
|
|
|
|
v2g_ctx->evse_v2g_data.central_contract_validation_allowed = central_contract_validation_allowed;
|
|
}
|
|
|
|
void ISO15118_chargerImpl::handle_bpt_setup(types::iso15118::BptSetup& bpt_config) {
|
|
EVLOG_warning << "Ignoring handle_bpt_setup call";
|
|
}
|
|
|
|
void ISO15118_chargerImpl::handle_set_powersupply_capabilities(types::power_supply_DC::Capabilities& capabilities) {
|
|
|
|
const auto max_export_current =
|
|
capabilities.nominal_max_export_current_A.value_or(capabilities.max_export_current_A);
|
|
const auto min_export_current =
|
|
capabilities.nominal_min_export_current_A.value_or(capabilities.min_export_current_A);
|
|
const auto max_export_power = capabilities.nominal_max_export_power_W.value_or(capabilities.max_export_power_W);
|
|
const auto max_export_voltage =
|
|
capabilities.nominal_max_export_voltage_V.value_or(capabilities.max_export_voltage_V);
|
|
const auto min_export_voltage =
|
|
capabilities.nominal_min_export_voltage_V.value_or(capabilities.min_export_voltage_V);
|
|
|
|
populate_physical_value_float(&v2g_ctx->evse_v2g_data.power_capabilities.max_current, max_export_current, 1,
|
|
iso2_unitSymbolType_A);
|
|
populate_physical_value_float(&v2g_ctx->evse_v2g_data.power_capabilities.min_current, min_export_current, 1,
|
|
iso2_unitSymbolType_A);
|
|
populate_physical_value(&v2g_ctx->evse_v2g_data.power_capabilities.max_power,
|
|
static_cast<uint32_t>(max_export_power), iso2_unitSymbolType_W);
|
|
populate_physical_value_float(&v2g_ctx->evse_v2g_data.power_capabilities.max_voltage, max_export_voltage, 1,
|
|
iso2_unitSymbolType_V);
|
|
populate_physical_value_float(&v2g_ctx->evse_v2g_data.power_capabilities.min_voltage, min_export_voltage, 1,
|
|
iso2_unitSymbolType_V);
|
|
}
|
|
|
|
void ISO15118_chargerImpl::handle_authorization_response(
|
|
types::authorization::AuthorizationStatus& authorization_status,
|
|
types::authorization::CertificateStatus& certificate_status) {
|
|
|
|
v2g_ctx->evse_v2g_data.evse_processing[PHASE_AUTH] = static_cast<uint8_t>(iso2_EVSEProcessingType_Finished);
|
|
|
|
if (authorization_status != types::authorization::AuthorizationStatus::Accepted) {
|
|
v2g_ctx->session.authorization_rejected = true;
|
|
}
|
|
|
|
if (v2g_ctx->session.iso_selected_payment_option == iso2_paymentOptionType_Contract) {
|
|
v2g_ctx->session.certificate_status = certificate_status;
|
|
}
|
|
}
|
|
|
|
void ISO15118_chargerImpl::handle_ac_contactor_closed(bool& status) {
|
|
/* signal changes to possible waiters, according to man page, it never returns an error code */
|
|
pthread_mutex_lock(&v2g_ctx->mqtt_lock);
|
|
v2g_ctx->contactor_is_closed = status;
|
|
pthread_cond_signal(&v2g_ctx->mqtt_cond);
|
|
/* unlock */
|
|
pthread_mutex_unlock(&v2g_ctx->mqtt_lock);
|
|
}
|
|
|
|
void ISO15118_chargerImpl::handle_dlink_ready(bool& value) {
|
|
sdp_set_dlink_ready(v2g_ctx, value);
|
|
|
|
// If dlink becomes not ready (false), stop TCP connection in the read thread
|
|
if (!value) {
|
|
v2g_ctx->is_connection_terminated = true;
|
|
}
|
|
}
|
|
|
|
void ISO15118_chargerImpl::handle_cable_check_finished(bool& status) {
|
|
if (status == true) {
|
|
v2g_ctx->evse_v2g_data.evse_processing[PHASE_ISOLATION] = (uint8_t)iso2_EVSEProcessingType_Finished;
|
|
} else {
|
|
v2g_ctx->evse_v2g_data.evse_processing[PHASE_ISOLATION] = (uint8_t)iso2_EVSEProcessingType_Ongoing;
|
|
}
|
|
}
|
|
|
|
void ISO15118_chargerImpl::handle_receipt_is_required(bool& receipt_required) {
|
|
v2g_ctx->evse_v2g_data.receipt_required = (int)receipt_required;
|
|
}
|
|
|
|
void ISO15118_chargerImpl::handle_stop_charging(bool& stop) {
|
|
// FIXME we need to use locks on v2g-ctx in all commands as they are running in different threads
|
|
|
|
if (stop) {
|
|
// spawn new thread to not block command handler
|
|
std::thread([stop] {
|
|
// try to gracefully shutdown charging session
|
|
v2g_ctx->evse_v2g_data.evse_notification = iso2_EVSENotificationType_StopCharging;
|
|
memset(v2g_ctx->evse_v2g_data.evse_status_code, iso2_DC_EVSEStatusCodeType_EVSE_Shutdown,
|
|
sizeof(v2g_ctx->evse_v2g_data.evse_status_code));
|
|
|
|
int i;
|
|
bool timeout_reached = true;
|
|
// allow 10 seconds for graceful shutdown
|
|
for (i = 0; i < 10; i++) {
|
|
if (v2g_ctx->is_connection_terminated) {
|
|
timeout_reached = false;
|
|
break;
|
|
}
|
|
std::this_thread::sleep_for(1s);
|
|
}
|
|
|
|
// If it did not stop within timeout, stop session using FAILED reply
|
|
if (timeout_reached) {
|
|
v2g_ctx->stop_hlc = stop;
|
|
}
|
|
}).detach();
|
|
} else {
|
|
v2g_ctx->stop_hlc = false;
|
|
}
|
|
}
|
|
|
|
void ISO15118_chargerImpl::handle_pause_charging(bool& pause) {
|
|
EVLOG_warning << "Pause initialized by the charger is not supported in DIN70121 and ISO15118-2";
|
|
}
|
|
|
|
void ISO15118_chargerImpl::handle_no_energy_pause_charging(types::iso15118::NoEnergyPauseMode& mode) {
|
|
|
|
switch (mode) {
|
|
case types::iso15118::NoEnergyPauseMode::PauseAfterPrecharge:
|
|
v2g_ctx->evse_v2g_data.no_energy_pause = NoEnergyPauseStatus::AfterCableCheckPreCharge;
|
|
break;
|
|
|
|
case types::iso15118::NoEnergyPauseMode::PauseBeforeCableCheck:
|
|
v2g_ctx->evse_v2g_data.no_energy_pause = NoEnergyPauseStatus::BeforeCableCheck;
|
|
break;
|
|
|
|
case types::iso15118::NoEnergyPauseMode::AllowEvToIgnorePause:
|
|
v2g_ctx->evse_v2g_data.no_energy_pause = NoEnergyPauseStatus::AllowEvToIgnorePause;
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool ISO15118_chargerImpl::handle_update_supported_app_protocols(
|
|
types::iso15118::SupportedAppProtocols& supported_app_protocols) {
|
|
bool rv{true};
|
|
v2g_ctx->supported_protocols = 0;
|
|
|
|
if (supported_app_protocols.app_protocols.empty()) {
|
|
dlog(DLOG_LEVEL_WARNING, "No supported app protocols configured");
|
|
return true;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
dlog(DLOG_LEVEL_INFO, "Configured charging protocols: [%s]", configured_protocols.c_str());
|
|
|
|
for (const auto& protocol : supported_app_protocols.app_protocols) {
|
|
// Check if the supported app protocol is in the SECC list
|
|
const bool allowed = std::find(this->supp_app_protocols_secc.app_protocols.begin(),
|
|
this->supp_app_protocols_secc.app_protocols.end(),
|
|
protocol) != this->supp_app_protocols_secc.app_protocols.end();
|
|
|
|
if (!allowed) {
|
|
dlog(DLOG_LEVEL_WARNING, "Skip unsupported app protocol: %s",
|
|
types::iso15118::supported_app_protocol_to_string(protocol).c_str());
|
|
rv = false;
|
|
continue;
|
|
}
|
|
/* Configure supported app bitmask. This bitmark is used in the supportedAppHandshake handle
|
|
to select the protocol */
|
|
switch (protocol) {
|
|
case types::iso15118::SupportedAppProtocol::DIN70121:
|
|
v2g_ctx->supported_protocols |= (1 << V2G_PROTO_DIN70121);
|
|
break;
|
|
case types::iso15118::SupportedAppProtocol::ISO15118D2:
|
|
v2g_ctx->supported_protocols |= (1 << V2G_PROTO_ISO15118_2013);
|
|
break;
|
|
case types::iso15118::SupportedAppProtocol::ISO15118D20:
|
|
default:
|
|
dlog(DLOG_LEVEL_WARNING, "Unsupported app protocol: %s",
|
|
types::iso15118::supported_app_protocol_to_string(protocol).c_str());
|
|
rv = false;
|
|
}
|
|
}
|
|
|
|
if (v2g_ctx->supported_protocols == 0) {
|
|
dlog(DLOG_LEVEL_WARNING, "No supported app protocols provided");
|
|
rv = false;
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
void ISO15118_chargerImpl::handle_update_energy_transfer_modes(
|
|
std::vector<types::iso15118::EnergyTransferMode>& supported_energy_transfer_modes) {
|
|
if (v2g_ctx->hlc_pause_active) {
|
|
return;
|
|
}
|
|
|
|
uint16_t& energyArrayLen =
|
|
(v2g_ctx->evse_v2g_data.charge_service.SupportedEnergyTransferMode.EnergyTransferMode.arrayLen);
|
|
iso2_EnergyTransferModeType* energyArray =
|
|
v2g_ctx->evse_v2g_data.charge_service.SupportedEnergyTransferMode.EnergyTransferMode.array;
|
|
energyArrayLen = 0;
|
|
|
|
v2g_ctx->is_dc_charger = true;
|
|
|
|
const auto max_supported_energy_modes =
|
|
std::min(static_cast<size_t>(iso2_EnergyTransferModeType_6_ARRAY_SIZE), supported_energy_transfer_modes.size());
|
|
|
|
for (size_t i = 0; i < max_supported_energy_modes; i++) {
|
|
const auto& mode = supported_energy_transfer_modes.at(i);
|
|
switch (mode) {
|
|
case types::iso15118::EnergyTransferMode::AC_single_phase_core:
|
|
energyArray[(energyArrayLen)++] = iso2_EnergyTransferModeType_AC_single_phase_core;
|
|
v2g_ctx->is_dc_charger = false;
|
|
break;
|
|
case types::iso15118::EnergyTransferMode::AC_three_phase_core:
|
|
energyArray[(energyArrayLen)++] = iso2_EnergyTransferModeType_AC_three_phase_core;
|
|
v2g_ctx->is_dc_charger = false;
|
|
break;
|
|
case types::iso15118::EnergyTransferMode::DC_core:
|
|
energyArray[(energyArrayLen)++] = iso2_EnergyTransferModeType_DC_core;
|
|
break;
|
|
case types::iso15118::EnergyTransferMode::DC_extended:
|
|
energyArray[(energyArrayLen)++] = iso2_EnergyTransferModeType_DC_extended;
|
|
break;
|
|
case types::iso15118::EnergyTransferMode::DC_combo_core:
|
|
energyArray[(energyArrayLen)++] = iso2_EnergyTransferModeType_DC_combo_core;
|
|
break;
|
|
case types::iso15118::EnergyTransferMode::DC_unique:
|
|
energyArray[(energyArrayLen)++] = iso2_EnergyTransferModeType_DC_unique;
|
|
break;
|
|
case types::iso15118::EnergyTransferMode::DC_ACDP_BPT:
|
|
case types::iso15118::EnergyTransferMode::AC_BPT:
|
|
case types::iso15118::EnergyTransferMode::AC_BPT_DER:
|
|
case types::iso15118::EnergyTransferMode::DC_BPT:
|
|
dlog(DLOG_LEVEL_INFO, "Ignoring bidirectional SupportedEnergyTransferMode");
|
|
default:
|
|
if (energyArrayLen == 0) {
|
|
|
|
dlog(DLOG_LEVEL_WARNING, "Unable to configure SupportedEnergyTransferMode %s",
|
|
types::iso15118::energy_transfer_mode_to_string(mode).c_str());
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (mod->config.supported_DIN70121 and not v2g_ctx->is_dc_charger) {
|
|
v2g_ctx->supported_protocols &= ~(1 << V2G_PROTO_DIN70121);
|
|
dlog(DLOG_LEVEL_WARNING, "Removed DIN70121 from the list of supported protocols as AC is enabled");
|
|
}
|
|
}
|
|
|
|
void ISO15118_chargerImpl::handle_update_ac_max_current(double& max_current) {
|
|
v2g_ctx->basic_config.evse_ac_current_limit = max_current;
|
|
}
|
|
|
|
void ISO15118_chargerImpl::handle_update_ac_parameters(types::iso15118::AcParameters& ac_parameters) {
|
|
static bool warning_shown = false;
|
|
if (not warning_shown) {
|
|
EVLOG_warning << "Ignoring handle_update_ac_parameters call";
|
|
warning_shown = true;
|
|
}
|
|
}
|
|
|
|
void ISO15118_chargerImpl::handle_update_ac_maximum_limits(types::iso15118::AcEvseMaximumPower& maximum_limits) {
|
|
if (v2g_ctx->basic_config.evse_ac_nominal_voltage > 0.0f) {
|
|
v2g_ctx->basic_config.evse_ac_nominal_current =
|
|
maximum_limits.charge_power.total / v2g_ctx->basic_config.evse_ac_nominal_voltage;
|
|
} else {
|
|
EVLOG_error << "Cannot compute nominal current: nominal voltage is 0";
|
|
}
|
|
}
|
|
|
|
void ISO15118_chargerImpl::handle_update_ac_minimum_limits(types::iso15118::AcEvseMinimumPower& minimum_limits) {
|
|
static bool warning_shown = false;
|
|
if (not warning_shown) {
|
|
EVLOG_warning << "Ignoring handle_update_ac_minimum_limits call";
|
|
warning_shown = true;
|
|
}
|
|
}
|
|
|
|
void ISO15118_chargerImpl::handle_update_ac_target_values(types::iso15118::AcTargetValues& target_values) {
|
|
static bool warning_shown = false;
|
|
if (not warning_shown) {
|
|
EVLOG_warning << "Ignoring handle_update_ac_target_values call";
|
|
warning_shown = true;
|
|
}
|
|
}
|
|
|
|
void ISO15118_chargerImpl::handle_update_ac_present_power(types::units::Power& present_power) {
|
|
static bool warning_shown = false;
|
|
if (not warning_shown) {
|
|
EVLOG_warning << "Ignoring handle_update_ac_present_power call";
|
|
warning_shown = true;
|
|
}
|
|
}
|
|
|
|
void ISO15118_chargerImpl::handle_update_dc_maximum_limits(types::iso15118::DcEvseMaximumLimits& maximum_limits) {
|
|
if (maximum_limits.evse_maximum_current_limit > 300.) {
|
|
populate_physical_value_float(&v2g_ctx->evse_v2g_data.evse_maximum_current_limit,
|
|
maximum_limits.evse_maximum_current_limit, 1, iso2_unitSymbolType_A);
|
|
} else {
|
|
populate_physical_value_float(&v2g_ctx->evse_v2g_data.evse_maximum_current_limit,
|
|
maximum_limits.evse_maximum_current_limit, 2, iso2_unitSymbolType_A);
|
|
}
|
|
v2g_ctx->evse_v2g_data.evse_maximum_current_limit_is_used = 1;
|
|
|
|
populate_physical_value(&v2g_ctx->evse_v2g_data.evse_maximum_power_limit, maximum_limits.evse_maximum_power_limit,
|
|
iso2_unitSymbolType_W);
|
|
v2g_ctx->evse_v2g_data.evse_maximum_power_limit_is_used = 1;
|
|
|
|
populate_physical_value_float(&v2g_ctx->evse_v2g_data.evse_maximum_voltage_limit,
|
|
maximum_limits.evse_maximum_voltage_limit, 1, iso2_unitSymbolType_V);
|
|
v2g_ctx->evse_v2g_data.evse_maximum_voltage_limit_is_used = 1;
|
|
}
|
|
|
|
void ISO15118_chargerImpl::handle_update_dc_minimum_limits(types::iso15118::DcEvseMinimumLimits& minimum_limits) {
|
|
|
|
populate_physical_value_float(&v2g_ctx->evse_v2g_data.evse_minimum_current_limit,
|
|
static_cast<long long int>(minimum_limits.evse_minimum_current_limit), 1,
|
|
iso2_unitSymbolType_A);
|
|
populate_physical_value_float(&v2g_ctx->evse_v2g_data.evse_minimum_voltage_limit,
|
|
static_cast<long long int>(minimum_limits.evse_minimum_voltage_limit), 1,
|
|
iso2_unitSymbolType_V);
|
|
}
|
|
|
|
void ISO15118_chargerImpl::handle_update_isolation_status(types::iso15118::IsolationStatus& isolation_status) {
|
|
v2g_ctx->evse_v2g_data.evse_isolation_status = (uint8_t)isolation_status;
|
|
v2g_ctx->evse_v2g_data.evse_isolation_status_is_used = 1;
|
|
}
|
|
|
|
void ISO15118_chargerImpl::handle_update_dc_present_values(
|
|
types::iso15118::DcEvsePresentVoltageCurrent& present_voltage_current) {
|
|
populate_physical_value_float(&v2g_ctx->evse_v2g_data.evse_present_voltage,
|
|
present_voltage_current.evse_present_voltage, 1, iso2_unitSymbolType_V);
|
|
|
|
if (present_voltage_current.evse_present_current.has_value()) {
|
|
populate_physical_value_float(&v2g_ctx->evse_v2g_data.evse_present_current,
|
|
static_cast<float>(present_voltage_current.evse_present_current.value()), 1,
|
|
iso2_unitSymbolType_A);
|
|
}
|
|
}
|
|
|
|
void ISO15118_chargerImpl::handle_update_meter_info(types::powermeter::Powermeter& powermeter) {
|
|
// Signal for ChargingStatus and CurrentDemand that MeterInfo is used
|
|
v2g_ctx->meter_info.meter_info_is_used = 1;
|
|
v2g_ctx->meter_info.meter_reading = powermeter.energy_Wh_import.total;
|
|
|
|
if (powermeter.meter_id) {
|
|
uint8_t len = powermeter.meter_id->length();
|
|
if (len < iso2_MeterID_CHARACTER_SIZE) {
|
|
memcpy(v2g_ctx->meter_info.meter_id.bytes, powermeter.meter_id->c_str(), len);
|
|
v2g_ctx->meter_info.meter_id.bytesLen = len;
|
|
} else {
|
|
dlog(DLOG_LEVEL_WARNING, "MeterID_CHARACTER_SIZEexceeded (received: %u, max: %u)", len,
|
|
iso2_MeterID_CHARACTER_SIZE);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ISO15118_chargerImpl::handle_send_error(types::iso15118::EvseError& error) {
|
|
switch (error) {
|
|
case types::iso15118::EvseError::Error_Contactor:
|
|
break;
|
|
case types::iso15118::EvseError::Error_RCD:
|
|
v2g_ctx->evse_v2g_data.rcd = 1;
|
|
break;
|
|
case types::iso15118::EvseError::Error_UtilityInterruptEvent:
|
|
memset(v2g_ctx->evse_v2g_data.evse_status_code, (int)iso2_DC_EVSEStatusCodeType_EVSE_UtilityInterruptEvent,
|
|
sizeof(v2g_ctx->evse_v2g_data.evse_status_code));
|
|
break;
|
|
case types::iso15118::EvseError::Error_Malfunction:
|
|
memset(v2g_ctx->evse_v2g_data.evse_status_code, (int)iso2_DC_EVSEStatusCodeType_EVSE_Malfunction,
|
|
sizeof(v2g_ctx->evse_v2g_data.evse_status_code));
|
|
break;
|
|
case types::iso15118::EvseError::Error_EmergencyShutdown:
|
|
/* signal changes to possible waiters, according to man page, it never returns an error code */
|
|
pthread_mutex_lock(&v2g_ctx->mqtt_lock);
|
|
v2g_ctx->intl_emergency_shutdown = true;
|
|
pthread_cond_signal(&v2g_ctx->mqtt_cond);
|
|
/* unlock */
|
|
pthread_mutex_unlock(&v2g_ctx->mqtt_lock);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void ISO15118_chargerImpl::handle_reset_error() {
|
|
v2g_ctx->evse_v2g_data.rcd = 0;
|
|
|
|
v2g_ctx->evse_v2g_data.evse_status_code[PHASE_INIT] = iso2_DC_EVSEStatusCodeType_EVSE_NotReady;
|
|
v2g_ctx->evse_v2g_data.evse_status_code[PHASE_AUTH] = iso2_DC_EVSEStatusCodeType_EVSE_NotReady;
|
|
v2g_ctx->evse_v2g_data.evse_status_code[PHASE_PARAMETER] = iso2_DC_EVSEStatusCodeType_EVSE_Ready; // [V2G-DC-453]
|
|
v2g_ctx->evse_v2g_data.evse_status_code[PHASE_ISOLATION] =
|
|
iso2_DC_EVSEStatusCodeType_EVSE_IsolationMonitoringActive;
|
|
v2g_ctx->evse_v2g_data.evse_status_code[PHASE_PRECHARGE] = iso2_DC_EVSEStatusCodeType_EVSE_Ready;
|
|
v2g_ctx->evse_v2g_data.evse_status_code[PHASE_CHARGE] = iso2_DC_EVSEStatusCodeType_EVSE_Ready;
|
|
v2g_ctx->evse_v2g_data.evse_status_code[PHASE_WELDING] = iso2_DC_EVSEStatusCodeType_EVSE_NotReady;
|
|
v2g_ctx->evse_v2g_data.evse_status_code[PHASE_STOP] = iso2_DC_EVSEStatusCodeType_EVSE_NotReady;
|
|
|
|
// Todo(sl): check if emergency should be cleared here?
|
|
}
|
|
|
|
} // namespace charger
|
|
} // namespace module
|