// 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 #include #include 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(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(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& 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(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(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(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& 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(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(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(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(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