// SPDX-License-Identifier: Apache-2.0 // Copyright (C) 2023 chargebyte GmbH // Copyright (C) 2023 Contributors to EVerest #include #include //for V2GTP_HEADER_LENGTHs #include #include #include #include #include #include #include #include #include #include #include #include "crypto/crypto_openssl.hpp" using namespace openssl; using namespace crypto::openssl; #include "iso_server.hpp" #include "log.hpp" #include "tools.hpp" #include "v2g_ctx.hpp" #include "v2g_server.hpp" #define MQTT_MAX_PAYLOAD_SIZE 268435455 #define V2G_SECC_MSG_CERTINSTALL_TIME 4500 constexpr uint16_t SAE_V2H = 28472; constexpr uint16_t SAE_V2G = 28473; /*! * \brief iso_validate_state This function checks whether the received message is expected and valid at this * point in the communication sequence state machine. The current V2G msg type must be set with the current V2G msg * state. [V2G2-538] * \param state is the current state of the charging session. * \param current_v2g_msg is the current handled V2G message. * \param is_dc_charging is \c true if it is a DC charging session. * \return Returns a iso2_responseCode with sequence error if current_v2g_msg is not expected, otherwise OK. */ static iso2_responseCodeType iso_validate_state(int state, enum V2gMsgTypeId current_v2g_msg, bool is_dc_charging) { int allowed_requests = (true == is_dc_charging) ? iso_dc_states[state].allowed_requests : iso_ac_states[state].allowed_requests; // dc_charging is determined in charge_parameter. dc return (allowed_requests & (1 << current_v2g_msg)) ? iso2_responseCodeType_OK : iso2_responseCodeType_FAILED_SequenceError; } /*! * \brief iso_validate_response_code This function checks if an external error has occurred (sequence error, user abort) * ... ). \param iso_response_code is a pointer to the current response code. The value will be modified if an external * error has occurred. * \param conn the structure with the external error information. * \return Returns \c V2G_EVENT_SEND_AND_TERMINATE if the charging must be terminated after sending the response * message, returns \c V2G_EVENT_TERMINATE_CONNECTION if charging must be aborted immediately and \c V2G_EVENT_NO_EVENT * if no error */ static v2g_event iso_validate_response_code(iso2_responseCodeType* const v2g_response_code, struct v2g_connection const* const conn) { enum v2g_event next_event = V2G_EVENT_NO_EVENT; iso2_responseCodeType response_code_tmp; if (conn->ctx->is_connection_terminated == true) { dlog(DLOG_LEVEL_ERROR, "Connection is terminated. Abort charging"); return V2G_EVENT_TERMINATE_CONNECTION; } /* If MQTT user abort or emergency shutdown has occurred */ if ((conn->ctx->stop_hlc == true) || (conn->ctx->intl_emergency_shutdown == true)) { *v2g_response_code = iso2_responseCodeType_FAILED; } /* [V2G2-460]: check whether the session id matches the expected one of the active session */ *v2g_response_code = ((conn->ctx->current_v2g_msg != V2G_SESSION_SETUP_MSG) && (conn->ctx->ev_v2g_data.received_session_id != 0) && (conn->ctx->evse_v2g_data.session_id != conn->ctx->ev_v2g_data.received_session_id)) ? iso2_responseCodeType_FAILED_UnknownSession : *v2g_response_code; /* [V2G-DC-390]: at this point we must check whether the given request is valid at this step; * the idea is that we catch this error in each function below to respond with a valid * encoded message; note, that the handler functions below must not access v2g_session in * error path, since it might not be set, yet! */ response_code_tmp = iso_validate_state(conn->ctx->state, conn->ctx->current_v2g_msg, conn->ctx->is_dc_charger); // [V2G2-538] *v2g_response_code = (response_code_tmp >= iso2_responseCodeType_FAILED) ? response_code_tmp : *v2g_response_code; /* [V2G2-539] SequenceError always terminates the connection regardless of configuration. * For other FAILED codes, termination depends on the terminate_connection_on_failed_response setting. */ if (*v2g_response_code == iso2_responseCodeType_FAILED_SequenceError || ((conn->ctx->terminate_connection_on_failed_response == true) && (*v2g_response_code >= iso2_responseCodeType_FAILED))) { next_event = V2G_EVENT_SEND_AND_TERMINATE; // [V2G2-539], [V2G2-034] Send response and terminate tcp-connection } /* log failed response code message */ if ((*v2g_response_code >= iso2_responseCodeType_FAILED) && (*v2g_response_code <= iso2_responseCodeType_FAILED_CertificateRevoked)) { dlog(DLOG_LEVEL_ERROR, "Failed response code detected for message \"%s\", error: %s", v2g_msg_type[conn->ctx->current_v2g_msg], isoResponse[*v2g_response_code]); } return next_event; } /*! * \brief populate_ac_evse_status This function configures the evse_status struct * \param ctx is the V2G context * \param evse_status is the destination struct */ static void populate_ac_evse_status(struct v2g_context* ctx, struct iso2_AC_EVSEStatusType* evse_status) { evse_status->EVSENotification = (iso2_EVSENotificationType)ctx->evse_v2g_data.evse_notification; evse_status->NotificationMaxDelay = ctx->evse_v2g_data.notification_max_delay; evse_status->RCD = ctx->evse_v2g_data.rcd; } /*! * \brief check_iso2_charging_profile_values This function checks if EV charging profile values are within permissible * ranges \param req is the PowerDeliveryReq \param res is the PowerDeliveryRes \param conn holds the structure with the * V2G msg pair \param sa_schedule_tuple_idx is the index of SA schedule tuple */ static void check_iso2_charging_profile_values(iso2_PowerDeliveryReqType* req, iso2_PowerDeliveryResType* res, v2g_connection* conn, uint8_t sa_schedule_tuple_idx) { if (req->ChargingProfile_isUsed == (unsigned int)1) { const struct iso2_PMaxScheduleType* evse_p_max_schedule = &conn->ctx->evse_v2g_data.evse_sa_schedule_list.SAScheduleTuple.array[sa_schedule_tuple_idx].PMaxSchedule; uint32_t ev_time_sum = 0; // Summed EV relative time interval uint32_t evse_time_sum = 0; // Summed EVSE relative time interval uint8_t evse_idx = 0; // Actual PMaxScheduleEntry index bool ev_time_is_within_profile_entry = false; /* Is true if the summed EV relative time interval is within the actual EVSE time interval */ /* Check if the EV ChargingProfileEntryStart time and PMax value fits with the provided EVSE PMaxScheduleEntry * list. [V2G2-293] */ for (uint8_t ev_idx = 0; ev_idx < req->ChargingProfile.ProfileEntry.arrayLen && (res->ResponseCode == iso2_responseCodeType_OK); ev_idx++) { ev_time_sum += req->ChargingProfile.ProfileEntry.array[ev_idx].ChargingProfileEntryStart; while (evse_idx < evse_p_max_schedule->PMaxScheduleEntry.arrayLen && (ev_time_is_within_profile_entry == false)) { /* Check if EV ChargingProfileEntryStart value is within one EVSE schedule entry. * The last element must be checked separately, because of the duration value */ /* If we found an entry which fits in the EVSE time schedule, check if the next EV time slot fits as * well Otherwise check if the next time interval fits in the EVSE time schedule */ evse_time_sum += evse_p_max_schedule->PMaxScheduleEntry.array[evse_idx].RelativeTimeInterval.start; /* Check the time intervals, in the last schedule element the duration value must be considered */ if (evse_idx < (evse_p_max_schedule->PMaxScheduleEntry.arrayLen - 1)) { ev_time_is_within_profile_entry = (ev_time_sum >= evse_time_sum) && (ev_time_sum < (evse_time_sum + evse_p_max_schedule->PMaxScheduleEntry.array[evse_idx + 1].RelativeTimeInterval.start)); } else { ev_time_is_within_profile_entry = (ev_time_sum >= evse_time_sum) && (ev_time_sum <= (evse_time_sum + evse_p_max_schedule->PMaxScheduleEntry.array[evse_idx].RelativeTimeInterval.duration_isUsed * evse_p_max_schedule->PMaxScheduleEntry.array[evse_idx].RelativeTimeInterval.duration)); } if (ev_time_is_within_profile_entry == true) { /* Check if ev ChargingProfileEntryMaxPower element is equal to or smaller than the limits in * respective elements of the PMaxScheduleType */ if ((req->ChargingProfile.ProfileEntry.array[ev_idx].ChargingProfileEntryMaxPower.Value * pow(10, req->ChargingProfile.ProfileEntry.array[ev_idx].ChargingProfileEntryMaxPower.Multiplier)) > (evse_p_max_schedule->PMaxScheduleEntry.array[evse_idx].PMax.Value * pow(10, evse_p_max_schedule->PMaxScheduleEntry.array[evse_idx].PMax.Multiplier))) { res->ResponseCode = iso2_responseCodeType_FAILED_ChargingProfileInvalid; // [V2G2-224] // [V2G2-225] [V2G2-478] // setting response code is commented because some EVs do not support schedules correctly dlog(DLOG_LEVEL_WARNING, "EV's charging profile is invalid (ChargingProfileEntryMaxPower too high)!"); break; } } /* If the last EVSE element is reached and ChargingProfileEntryStart time doesn't fit */ else if (evse_idx == (evse_p_max_schedule->PMaxScheduleEntry.arrayLen - 1)) { // res->ResponseCode = iso2_responseCodeType_FAILED_ChargingProfileInvalid; // EV charing profile // time exceeds EVSE provided schedule // setting response code is commented because some EVs do not support schedules correctly dlog(DLOG_LEVEL_WARNING, "EV's charging profile is invalid (EV charging profile time exceeds provided schedule)!"); } else { /* Now we checked if the current EV interval fits within the EVSE interval, but it fails. * Next step is to check the EVSE interval until we reached the last EVSE interval */ evse_idx++; } } } } } static void publish_DcEvStatus(struct v2g_context* ctx, const struct iso2_DC_EVStatusType& iso2_ev_status) { if ((ctx->ev_v2g_data.iso2_dc_ev_status.EVErrorCode != iso2_ev_status.EVErrorCode) || (ctx->ev_v2g_data.iso2_dc_ev_status.EVReady != iso2_ev_status.EVReady) || (ctx->ev_v2g_data.iso2_dc_ev_status.EVRESSSOC != iso2_ev_status.EVRESSSOC)) { ctx->ev_v2g_data.iso2_dc_ev_status.EVErrorCode = iso2_ev_status.EVErrorCode; ctx->ev_v2g_data.iso2_dc_ev_status.EVReady = iso2_ev_status.EVReady; ctx->ev_v2g_data.iso2_dc_ev_status.EVRESSSOC = iso2_ev_status.EVRESSSOC; types::iso15118::DcEvStatus ev_status; ev_status.dc_ev_error_code = static_cast(iso2_ev_status.EVErrorCode); ev_status.dc_ev_ready = iso2_ev_status.EVReady; ev_status.dc_ev_ress_soc = static_cast(iso2_ev_status.EVRESSSOC); ctx->p_charger->publish_dc_ev_status(ev_status); } } static auto get_emergency_status_code(const struct v2g_context* ctx, uint8_t phase_type) { if (ctx->intl_emergency_shutdown) return iso2_DC_EVSEStatusCodeType_EVSE_EmergencyShutdown; else return static_cast(ctx->evse_v2g_data.evse_status_code[phase_type]); } //============================================= // Publishing request msg //============================================= /*! * \brief publish_iso_service_discovery_req This function publishes the iso_service_discovery_req message to the MQTT * interface. \param iso2_ServiceDiscoveryReqType is the request message. */ static void publish_iso_service_discovery_req(struct iso2_ServiceDiscoveryReqType const* const v2g_service_discovery_req) { // V2G values that can be published: ServiceCategory, ServiceScope } /*! * \brief publish_iso_service_detail_req This function publishes the iso_service_detail_req message to the MQTT * interface. \param v2g_service_detail_req is the request message. */ static void publish_iso_service_detail_req(struct iso2_ServiceDetailReqType const* const v2g_service_detail_req) { // V2G values that can be published: ServiceID } /*! * \brief publish_iso_payment_service_selection_req This function publishes the iso_payment_service_selection_req * message to the MQTT interface. * \param v2g_payment_service_selection_req is the request message. */ static void publish_iso_payment_service_selection_req( struct iso2_PaymentServiceSelectionReqType const* const v2g_payment_service_selection_req) { // V2G values that can be published: selected_payment_option, SelectedServiceList } /*! * \brief publish_iso_authorization_req This function publishes the publish_iso_authorization_req message to the MQTT * interface. \param v2g_authorization_req is the request message. */ static void publish_iso_authorization_req(struct iso2_AuthorizationReqType const* const v2g_authorization_req) { // V2G values that can be published: Id, Id_isUsed, GenChallenge, GenChallenge_isUsed } /*! * \brief publish_iso_charge_parameter_discovery_req This function publishes the charge_parameter_discovery_req message * to the MQTT interface. \param ctx is the V2G context. \param v2g_charge_parameter_discovery_req is the request * message. */ static void publish_iso_charge_parameter_discovery_req( struct v2g_context* ctx, struct iso2_ChargeParameterDiscoveryReqType const* const v2g_charge_parameter_discovery_req) { // Charging needs for OCPP types::iso15118::ChargingNeeds charging_needs; // V2G values that can be published: DC_EVChargeParameter, MaxEntriesSAScheduleTuple types::iso15118::EnergyTransferMode transfer_mode{}; switch (v2g_charge_parameter_discovery_req->RequestedEnergyTransferMode) { case iso2_EnergyTransferModeType_AC_single_phase_core: transfer_mode = types::iso15118::EnergyTransferMode::AC_single_phase_core; break; case iso2_EnergyTransferModeType_AC_three_phase_core: transfer_mode = types::iso15118::EnergyTransferMode::AC_three_phase_core; break; case iso2_EnergyTransferModeType_DC_core: transfer_mode = types::iso15118::EnergyTransferMode::DC_core; break; case iso2_EnergyTransferModeType_DC_extended: transfer_mode = types::iso15118::EnergyTransferMode::DC_extended; break; case iso2_EnergyTransferModeType_DC_combo_core: transfer_mode = types::iso15118::EnergyTransferMode::DC_combo_core; break; case iso2_EnergyTransferModeType_DC_unique: transfer_mode = types::iso15118::EnergyTransferMode::DC_unique; break; default: dlog(DLOG_LEVEL_WARNING, "Unable to convert RequestedEnergyTransferMode to EnergyTransferMode: %d", v2g_charge_parameter_discovery_req->RequestedEnergyTransferMode); } charging_needs.requested_energy_transfer = transfer_mode; ctx->p_charger->publish_requested_energy_transfer_mode(transfer_mode); if (v2g_charge_parameter_discovery_req->AC_EVChargeParameter_isUsed == (unsigned int)1) { if (v2g_charge_parameter_discovery_req->AC_EVChargeParameter.DepartureTime_isUsed == (unsigned int)1) { const char* format = "%Y-%m-%dT%H:%M:%SZ"; char buffer[100]; std::time_t time_now_in_sec = time(NULL); std::time_t departure_time = time_now_in_sec + v2g_charge_parameter_discovery_req->AC_EVChargeParameter.DepartureTime; std::strftime(buffer, sizeof(buffer), format, std::gmtime(&departure_time)); ctx->p_charger->publish_departure_time(buffer); } // TODO(ioan): calc physical once float ac_eamount = calc_physical_value(v2g_charge_parameter_discovery_req->AC_EVChargeParameter.EAmount.Value, v2g_charge_parameter_discovery_req->AC_EVChargeParameter.EAmount.Multiplier); float ac_ev_max_voltage = calc_physical_value(v2g_charge_parameter_discovery_req->AC_EVChargeParameter.EVMaxVoltage.Value, v2g_charge_parameter_discovery_req->AC_EVChargeParameter.EVMaxVoltage.Multiplier); float ac_ev_max_current = calc_physical_value(v2g_charge_parameter_discovery_req->AC_EVChargeParameter.EVMaxCurrent.Value, v2g_charge_parameter_discovery_req->AC_EVChargeParameter.EVMaxCurrent.Multiplier); float ac_ev_min_current = calc_physical_value(v2g_charge_parameter_discovery_req->AC_EVChargeParameter.EVMinCurrent.Value, v2g_charge_parameter_discovery_req->AC_EVChargeParameter.EVMinCurrent.Multiplier); ctx->p_charger->publish_ac_eamount(ac_eamount); ctx->p_charger->publish_ac_ev_max_voltage(ac_ev_max_voltage); ctx->p_charger->publish_ac_ev_max_current(ac_ev_max_current); ctx->p_charger->publish_ac_ev_min_current(ac_ev_min_current); auto& ac_charging_parameters = charging_needs.ac_charging_parameters.emplace(); // We do not require to calc a min/max here ac_charging_parameters.energy_amount = ac_eamount; ac_charging_parameters.ev_max_voltage = ac_ev_max_voltage; ac_charging_parameters.ev_max_current = ac_ev_max_current; ac_charging_parameters.ev_min_current = ac_ev_min_current; } else if (v2g_charge_parameter_discovery_req->DC_EVChargeParameter_isUsed == (unsigned int)1) { if (v2g_charge_parameter_discovery_req->DC_EVChargeParameter.DepartureTime_isUsed == (unsigned int)1) { const char* format = "%Y-%m-%dT%H:%M:%SZ"; char buffer[100]; std::time_t time_now_in_sec = time(NULL); std::time_t departure_time = time_now_in_sec + v2g_charge_parameter_discovery_req->DC_EVChargeParameter.DepartureTime; std::strftime(buffer, sizeof(buffer), format, std::gmtime(&departure_time)); ctx->p_charger->publish_departure_time(buffer); } auto& dc_charging_parameters = charging_needs.dc_charging_parameters.emplace(); if (v2g_charge_parameter_discovery_req->DC_EVChargeParameter.EVEnergyCapacity_isUsed == (unsigned int)1) { ctx->p_charger->publish_dc_ev_energy_capacity(calc_physical_value( v2g_charge_parameter_discovery_req->DC_EVChargeParameter.EVEnergyCapacity.Value, v2g_charge_parameter_discovery_req->DC_EVChargeParameter.EVEnergyCapacity.Multiplier)); dc_charging_parameters.ev_energy_capacity = calc_physical_value( v2g_charge_parameter_discovery_req->DC_EVChargeParameter.EVEnergyCapacity.Value, v2g_charge_parameter_discovery_req->DC_EVChargeParameter.EVEnergyCapacity.Multiplier); } if (v2g_charge_parameter_discovery_req->DC_EVChargeParameter.EVEnergyRequest_isUsed == (unsigned int)1) { ctx->p_charger->publish_dc_ev_energy_request(calc_physical_value( v2g_charge_parameter_discovery_req->DC_EVChargeParameter.EVEnergyRequest.Value, v2g_charge_parameter_discovery_req->DC_EVChargeParameter.EVEnergyRequest.Multiplier)); // OCPP2.1 Spec: Relates to: ISO 15118-2: DC_EVChargeParameterType: EVEnergyRequest dc_charging_parameters.energy_amount = calc_physical_value( v2g_charge_parameter_discovery_req->DC_EVChargeParameter.EVEnergyRequest.Value, v2g_charge_parameter_discovery_req->DC_EVChargeParameter.EVEnergyRequest.Multiplier); } if (v2g_charge_parameter_discovery_req->DC_EVChargeParameter.FullSOC_isUsed == (unsigned int)1) { ctx->p_charger->publish_dc_full_soc(v2g_charge_parameter_discovery_req->DC_EVChargeParameter.FullSOC); dc_charging_parameters.full_soc = v2g_charge_parameter_discovery_req->DC_EVChargeParameter.FullSOC; } if (v2g_charge_parameter_discovery_req->DC_EVChargeParameter.BulkSOC_isUsed == (unsigned int)1) { ctx->p_charger->publish_dc_bulk_soc(v2g_charge_parameter_discovery_req->DC_EVChargeParameter.BulkSOC); dc_charging_parameters.bulk_soc = v2g_charge_parameter_discovery_req->DC_EVChargeParameter.BulkSOC; } float evMaximumCurrentLimit = calc_physical_value( v2g_charge_parameter_discovery_req->DC_EVChargeParameter.EVMaximumCurrentLimit.Value, v2g_charge_parameter_discovery_req->DC_EVChargeParameter.EVMaximumCurrentLimit.Multiplier); float evMaximumPowerLimit = calc_physical_value( v2g_charge_parameter_discovery_req->DC_EVChargeParameter.EVMaximumPowerLimit.Value, v2g_charge_parameter_discovery_req->DC_EVChargeParameter.EVMaximumPowerLimit.Multiplier); float evMaximumVoltageLimit = calc_physical_value( v2g_charge_parameter_discovery_req->DC_EVChargeParameter.EVMaximumVoltageLimit.Value, v2g_charge_parameter_discovery_req->DC_EVChargeParameter.EVMaximumVoltageLimit.Multiplier); publish_dc_ev_maximum_limits( ctx, evMaximumCurrentLimit, (unsigned int)1, evMaximumPowerLimit, v2g_charge_parameter_discovery_req->DC_EVChargeParameter.EVMaximumPowerLimit_isUsed, evMaximumVoltageLimit, (unsigned int)1); publish_DcEvStatus(ctx, v2g_charge_parameter_discovery_req->DC_EVChargeParameter.DC_EVStatus); dc_charging_parameters.ev_max_current = evMaximumCurrentLimit; dc_charging_parameters.ev_max_power = evMaximumPowerLimit; dc_charging_parameters.ev_max_voltage = evMaximumVoltageLimit; dc_charging_parameters.state_of_charge = v2g_charge_parameter_discovery_req->DC_EVChargeParameter.DC_EVStatus.EVRESSSOC; } // Publish charging needs ctx->p_extensions->publish_charging_needs(charging_needs); } /*! * \brief publish_iso_pre_charge_req This function publishes the iso_pre_charge_req message to the MQTT interface. * \param ctx is the V2G context. * \param v2g_precharge_req is the request message. */ static void publish_iso_pre_charge_req(struct v2g_context* ctx, struct iso2_PreChargeReqType const* const v2g_precharge_req) { publish_dc_ev_target_voltage_current( ctx, calc_physical_value(v2g_precharge_req->EVTargetVoltage.Value, v2g_precharge_req->EVTargetVoltage.Multiplier), calc_physical_value(v2g_precharge_req->EVTargetCurrent.Value, v2g_precharge_req->EVTargetCurrent.Multiplier)); publish_DcEvStatus(ctx, v2g_precharge_req->DC_EVStatus); } /*! * \brief publish_iso_power_delivery_req This function publishes the iso_power_delivery_req message to the MQTT * interface. \param ctx is the V2G context. \param v2g_power_delivery_req is the request message. */ static void publish_iso_power_delivery_req(struct v2g_context* ctx, struct iso2_PowerDeliveryReqType const* const v2g_power_delivery_req) { // V2G values that can be published: ChargeProgress, SAScheduleTupleID if (v2g_power_delivery_req->DC_EVPowerDeliveryParameter_isUsed == (unsigned int)1) { ctx->p_charger->publish_dc_charging_complete( v2g_power_delivery_req->DC_EVPowerDeliveryParameter.ChargingComplete); if (v2g_power_delivery_req->DC_EVPowerDeliveryParameter.BulkChargingComplete_isUsed == (unsigned int)1) { ctx->p_charger->publish_dc_bulk_charging_complete( v2g_power_delivery_req->DC_EVPowerDeliveryParameter.BulkChargingComplete); } publish_DcEvStatus(ctx, v2g_power_delivery_req->DC_EVPowerDeliveryParameter.DC_EVStatus); } } /*! * \brief publish_iso_current_demand_req This function publishes the iso_current_demand_req message to the MQTT * interface. \param ctx is the V2G context \param v2g_current_demand_req is the request message. */ static void publish_iso_current_demand_req(struct v2g_context* ctx, struct iso2_CurrentDemandReqType const* const v2g_current_demand_req) { if ((v2g_current_demand_req->BulkChargingComplete_isUsed == (unsigned int)1) && (ctx->ev_v2g_data.bulk_charging_complete != v2g_current_demand_req->BulkChargingComplete)) { ctx->p_charger->publish_dc_bulk_charging_complete(v2g_current_demand_req->BulkChargingComplete); ctx->ev_v2g_data.bulk_charging_complete = v2g_current_demand_req->BulkChargingComplete; } if (ctx->ev_v2g_data.charging_complete != v2g_current_demand_req->ChargingComplete) { ctx->p_charger->publish_dc_charging_complete(v2g_current_demand_req->ChargingComplete); ctx->ev_v2g_data.charging_complete = v2g_current_demand_req->ChargingComplete; } publish_DcEvStatus(ctx, v2g_current_demand_req->DC_EVStatus); publish_dc_ev_target_voltage_current(ctx, calc_physical_value(v2g_current_demand_req->EVTargetVoltage.Value, v2g_current_demand_req->EVTargetVoltage.Multiplier), calc_physical_value(v2g_current_demand_req->EVTargetCurrent.Value, v2g_current_demand_req->EVTargetCurrent.Multiplier)); float evMaximumCurrentLimit = calc_physical_value(v2g_current_demand_req->EVMaximumCurrentLimit.Value, v2g_current_demand_req->EVMaximumCurrentLimit.Multiplier); float evMaximumPowerLimit = calc_physical_value(v2g_current_demand_req->EVMaximumPowerLimit.Value, v2g_current_demand_req->EVMaximumPowerLimit.Multiplier); float evMaximumVoltageLimit = calc_physical_value(v2g_current_demand_req->EVMaximumVoltageLimit.Value, v2g_current_demand_req->EVMaximumVoltageLimit.Multiplier); publish_dc_ev_maximum_limits(ctx, evMaximumCurrentLimit, v2g_current_demand_req->EVMaximumCurrentLimit_isUsed, evMaximumPowerLimit, v2g_current_demand_req->EVMaximumPowerLimit_isUsed, evMaximumVoltageLimit, v2g_current_demand_req->EVMaximumVoltageLimit_isUsed); float v2g_dc_ev_remaining_time_to_full_soc = calc_physical_value(v2g_current_demand_req->RemainingTimeToFullSoC.Value, v2g_current_demand_req->RemainingTimeToFullSoC.Multiplier); float v2g_dc_ev_remaining_time_to_bulk_soc = calc_physical_value(v2g_current_demand_req->RemainingTimeToBulkSoC.Value, v2g_current_demand_req->RemainingTimeToBulkSoC.Multiplier); publish_dc_ev_remaining_time( ctx, v2g_dc_ev_remaining_time_to_full_soc, v2g_current_demand_req->RemainingTimeToFullSoC_isUsed, v2g_dc_ev_remaining_time_to_bulk_soc, v2g_current_demand_req->RemainingTimeToBulkSoC_isUsed); } /*! * \brief publish_iso_metering_receipt_req This function publishes the iso_metering_receipt_req message to the MQTT * interface. \param v2g_metering_receipt_req is the request message. */ static void publish_iso_metering_receipt_req(struct iso2_MeteringReceiptReqType const* const v2g_metering_receipt_req) { // TODO: publish PnC only } /*! * \brief publish_iso_welding_detection_req This function publishes the iso_welding_detection_req message to the MQTT * interface. \param p_charger to publish MQTT topics. \param v2g_welding_detection_req is the request message. */ static void publish_iso_welding_detection_req(struct v2g_context* ctx, struct iso2_WeldingDetectionReqType const* const v2g_welding_detection_req) { // TODO: V2G values that can be published: EVErrorCode, EVReady, EVRESSSOC publish_DcEvStatus(ctx, v2g_welding_detection_req->DC_EVStatus); } /*! * \brief publish_iso_certificate_installation_exi_req This function publishes the iso_certificate_update_req message to * the MQTT interface. \param AExiBuffer is the exi msg where the V2G EXI msg is stored. \param AExiBufferSize is the * size of the V2G msg. \return Returns \c true if it was successful, otherwise \c false. */ static bool publish_iso_certificate_installation_exi_req(struct v2g_context* ctx, uint8_t* AExiBuffer, size_t AExiBufferSize) { // PnC only bool rv = true; types::iso15118::RequestExiStreamSchema certificate_request; certificate_request.exi_request = openssl::base64_encode(AExiBuffer, AExiBufferSize); if (certificate_request.exi_request.size() > MQTT_MAX_PAYLOAD_SIZE) { dlog(DLOG_LEVEL_ERROR, "Mqtt payload size exceeded!"); return false; } if (certificate_request.exi_request.size() == 0) { dlog(DLOG_LEVEL_ERROR, "Unable to encode contract leaf certificate"); return false; } certificate_request.iso15118_schema_version = ISO_15118_2013_MSG_DEF; certificate_request.certificate_action = types::iso15118::CertificateActionEnum::Install; ctx->p_extensions->publish_iso15118_certificate_request(certificate_request); return rv; } //============================================= // Request Handling //============================================= /*! * \brief handle_iso_session_setup This function handles the iso_session_setup msg pair. It analyzes the request msg and * fills the response msg. \param conn holds the structure with the V2G msg pair. \return Returns the next V2G-event. */ static enum v2g_event handle_iso_session_setup(struct v2g_connection* conn) { struct iso2_SessionSetupReqType* req = &conn->exi_in.iso2EXIDocument->V2G_Message.Body.SessionSetupReq; struct iso2_SessionSetupResType* res = &conn->exi_out.iso2EXIDocument->V2G_Message.Body.SessionSetupRes; enum v2g_event next_event = V2G_EVENT_NO_EVENT; /* format EVCC ID */ const auto mac_addr = to_mac_address_str(&req->EVCCID.bytes[0], req->EVCCID.bytesLen); conn->ctx->p_charger->publish_evcc_id(mac_addr); // publish EVCC ID dlog(DLOG_LEVEL_INFO, "SessionSetupReq.EVCCID: %s", (mac_addr.empty()) ? "(zero length provided)" : mac_addr.c_str()); /* [V2G2-756]: If the SECC receives a SessionSetupReq including a SessionID value which is not * equal to zero (0) and not equal to the SessionID value stored from the preceding V2G * Communication Session, it shall send a SessionID value in the SessionSetupRes message that is * unequal to "0" and unequal to the SessionID value stored from the preceding V2G Communication * Session and indicate the new V2G Communication Session with the ResponseCode set to * "OK_NewSessionEstablished" */ // TODO: handle resuming sessions [V2G2-463] /* Now fill the evse response message */ res->ResponseCode = iso2_responseCodeType_OK_NewSessionEstablished; /* Check and init session id */ /* If no session id is configured, generate one */ srand((unsigned int)time(NULL)); if (conn->ctx->evse_v2g_data.session_id == (uint64_t)0 || conn->ctx->evse_v2g_data.session_id != conn->ctx->ev_v2g_data.received_session_id) { generate_random_data(&conn->ctx->evse_v2g_data.session_id, 8); dlog( DLOG_LEVEL_INFO, "No session_id found or not equal to the id from the preceding v2g session. Generating random session id."); dlog(DLOG_LEVEL_INFO, "Created new session with id 0x%016" PRIx64, be64toh(conn->ctx->evse_v2g_data.session_id)); } else { dlog(DLOG_LEVEL_INFO, "Found Session_id from the old session: 0x%016" PRIx64, be64toh(conn->ctx->evse_v2g_data.session_id)); res->ResponseCode = iso2_responseCodeType_OK_OldSessionJoined; } /* TODO: publish EVCCID to MQTT */ // An overflow check is already done in ISO15118_chargerImpl.cpp res->EVSEID.charactersLen = conn->ctx->evse_v2g_data.evse_id.bytesLen; memcpy(res->EVSEID.characters, conn->ctx->evse_v2g_data.evse_id.bytes, conn->ctx->evse_v2g_data.evse_id.bytesLen); res->EVSETimeStamp_isUsed = conn->ctx->evse_v2g_data.date_time_now_is_used; res->EVSETimeStamp = time(NULL); /* Check the current response code and check if no external error has occurred */ next_event = (v2g_event)iso_validate_response_code(&res->ResponseCode, conn); /* Set next expected req msg */ conn->ctx->state = (int)iso_dc_state_id::WAIT_FOR_SERVICEDISCOVERY; // [V2G-543] return next_event; } /*! * \brief handle_iso_service_discovery This function handles the iso service discovery msg pair. It analyzes the request * msg and fills the response msg. The request and response msg based on the open V2G structures. This structures must * be provided within the \c conn structure. * \param conn holds the structure with the V2G msg pair. * \return Returns the next V2G-event. */ static enum v2g_event handle_iso_service_discovery(struct v2g_connection* conn) { struct iso2_ServiceDiscoveryReqType* req = &conn->exi_in.iso2EXIDocument->V2G_Message.Body.ServiceDiscoveryReq; struct iso2_ServiceDiscoveryResType* res = &conn->exi_out.iso2EXIDocument->V2G_Message.Body.ServiceDiscoveryRes; enum v2g_event nextEvent = V2G_EVENT_NO_EVENT; /* At first, publish the received ev request message to the MQTT interface */ publish_iso_service_discovery_req(req); /* build up response */ res->ResponseCode = iso2_responseCodeType_OK; // Checking of the charge service id if (conn->ctx->evse_v2g_data.charge_service.ServiceID != V2G_SERVICE_ID_CHARGING) { dlog(DLOG_LEVEL_WARNING, "Selected ServiceID is not ISO15118 conform. Correcting value to '1' (Charge service id)"); conn->ctx->evse_v2g_data.charge_service.ServiceID = V2G_SERVICE_ID_CHARGING; } // Checking of the service category if (conn->ctx->evse_v2g_data.charge_service.ServiceCategory != iso2_serviceCategoryType_EVCharging) { dlog(DLOG_LEVEL_WARNING, "Selected ServiceCategory is not ISO15118 conform. Correcting value to '0' (EVCharging)"); conn->ctx->evse_v2g_data.charge_service.ServiceCategory = iso2_serviceCategoryType_EVCharging; } res->ChargeService = conn->ctx->evse_v2g_data.charge_service; // Checking of the payment options const auto pnc_enabled = std::find(conn->ctx->evse_v2g_data.payment_option_list.begin(), conn->ctx->evse_v2g_data.payment_option_list.end(), iso2_paymentOptionType_Contract) != conn->ctx->evse_v2g_data.payment_option_list.end(); if (not conn->is_tls_connection and pnc_enabled) { conn->ctx->evse_v2g_data.payment_option_list = {iso2_paymentOptionType_ExternalPayment}; dlog(DLOG_LEVEL_WARNING, "PnC is not allowed without TLS-communication. Correcting value to '1' (ExternalPayment)"); } const auto payment_option_length = std::min(conn->ctx->evse_v2g_data.payment_option_list.size(), static_cast(iso2_paymentOptionType_2_ARRAY_SIZE)); memcpy(res->PaymentOptionList.PaymentOption.array, conn->ctx->evse_v2g_data.payment_option_list.data(), payment_option_length * sizeof(iso2_paymentOptionType)); res->PaymentOptionList.PaymentOption.arrayLen = payment_option_length; // ensure a "clean" service list res->ServiceList.Service.arrayLen = 0; // consider all services by default, but... for (const auto& service : conn->ctx->evse_v2g_data.evse_service_list) { // ...skip the service if the (possibly set) scope does not match a (possibly) requested one if (req->ServiceScope_isUsed and service.ServiceScope_isUsed and strncmp(req->ServiceScope.characters, service.ServiceScope.characters, sizeof(req->ServiceScope.characters)) != 0) continue; // ...skip if a possibly requested service category does not match the category of the service if (req->ServiceCategory_isUsed and req->ServiceCategory != service.ServiceCategory) continue; // copy otherwise res->ServiceList.Service.array[res->ServiceList.Service.arrayLen] = service; // update the new size res->ServiceList.Service.arrayLen++; } // tell the EXI encoder that we want to use the filled list res->ServiceList_isUsed = res->ServiceList.Service.arrayLen ? 1 : 0; /* Check the current response code and check if no external error has occurred */ nextEvent = (v2g_event)iso_validate_response_code(&res->ResponseCode, conn); /* Set next expected req msg */ conn->ctx->state = (int)iso_dc_state_id::WAIT_FOR_SVCDETAIL_PAYMENTSVCSEL; // [V2G-545] return nextEvent; } /*! * \brief handle_iso_service_detail This function handles the iso_service_detail msg pair. It analyzes the request msg * and fills the response msg. The request and response msg based on the open V2G structures. This structures must be * provided within the \c conn structure. (Optional VAS) * \param conn holds the structure with the V2G msg pair. * \return Returns the next V2G-event. */ static enum v2g_event handle_iso_service_detail(struct v2g_connection* conn) { struct iso2_ServiceDetailReqType* req = &conn->exi_in.iso2EXIDocument->V2G_Message.Body.ServiceDetailReq; struct iso2_ServiceDetailResType* res = &conn->exi_out.iso2EXIDocument->V2G_Message.Body.ServiceDetailRes; enum v2g_event next_event = V2G_EVENT_NO_EVENT; /* At first, publish the received ev request message to the MQTT interface */ publish_iso_service_detail_req(req); res->ResponseCode = iso2_responseCodeType_OK; /* ServiceID reported back always matches the requested one */ res->ServiceID = req->ServiceID; bool service_id_found = false; for (uint8_t idx = 0; idx < conn->ctx->evse_v2g_data.evse_service_list.size(); idx++) { if (req->ServiceID == conn->ctx->evse_v2g_data.evse_service_list[idx].ServiceID) { service_id_found = true; init_iso2_ServiceParameterListType(&res->ServiceParameterList); /* Fill parameter list of the requested service id [V2G2-549] */ if (conn->ctx->evse_v2g_data.service_parameter_list.find(req->ServiceID) != conn->ctx->evse_v2g_data.service_parameter_list.end()) { res->ServiceParameterList.ParameterSet = conn->ctx->evse_v2g_data.service_parameter_list.at(req->ServiceID).ParameterSet; res->ServiceParameterList_isUsed = true; break; } for (size_t i = 0; i < conn->ctx->supported_vas_services_per_provider.size(); i++) { const auto& provider_services = conn->ctx->supported_vas_services_per_provider.at(i); if (std::find(provider_services.begin(), provider_services.end(), req->ServiceID) != provider_services.end()) { const auto vas_parameters = conn->ctx->r_vas.at(i)->call_get_service_parameters(req->ServiceID); if (vas_parameters.empty()) { break; } iso2_ServiceParameterListType vas_parameter_list{}; init_iso2_ServiceParameterListType(&vas_parameter_list); size_t maxParameterSetArrayLen = ARRAY_SIZE(vas_parameter_list.ParameterSet.array); vas_parameter_list.ParameterSet.arrayLen = std::min(maxParameterSetArrayLen, vas_parameters.size()); for (size_t j = 0; j < vas_parameters.size() and j < maxParameterSetArrayLen; j++) { const auto& vas_parameter = vas_parameters.at(j); vas_parameter_list.ParameterSet.array[j].ParameterSetID = static_cast(vas_parameter.set_id); size_t t{0}; for (const auto& parameter : vas_parameter.parameters) { // quit loop when the maximum count of elements our encoder can handle is reached if (t == ARRAY_SIZE(vas_parameter_list.ParameterSet.array[j].Parameter.array)) break; auto& out_parameter = vas_parameter_list.ParameterSet.array[j].Parameter.array[t++]; init_iso2_ParameterType(&out_parameter); strncpy_to_v2g(out_parameter.Name.characters, sizeof(out_parameter.Name.characters), &out_parameter.Name.charactersLen, parameter.name); if (parameter.value.int_value.has_value()) { out_parameter.intValue = parameter.value.int_value.value(); out_parameter.intValue_isUsed = true; } else if (parameter.value.finite_string.has_value()) { const auto& temp = parameter.value.finite_string.value(); if (temp.length() > sizeof(out_parameter.stringValue.characters)) { EVLOG_warning << fmt::format("The value of parameter string '{}' is too long and " "was truncated from '{}' to '{:.{}}'", parameter.name, temp, temp, sizeof(out_parameter.stringValue.characters)); } strncpy_to_v2g(out_parameter.stringValue.characters, sizeof(out_parameter.stringValue.characters), &out_parameter.stringValue.charactersLen, temp); out_parameter.stringValue_isUsed = true; } else if (parameter.value.bool_value.has_value()) { out_parameter.boolValue = static_cast(parameter.value.bool_value.value()); out_parameter.boolValue_isUsed = true; } else if (parameter.value.byte_value.has_value()) { out_parameter.byteValue = static_cast(parameter.value.byte_value.value()); out_parameter.byteValue_isUsed = true; } else if (parameter.value.short_value.has_value()) { out_parameter.shortValue = static_cast(parameter.value.short_value.value()); out_parameter.shortValue_isUsed = true; } else if (parameter.value.rational_number.has_value()) { // TODO(SL): Specify the correct unit symbol populate_physical_value_float(&out_parameter.physicalValue, parameter.value.rational_number.value(), 1, iso2_unitSymbolType::iso2_unitSymbolType_W); out_parameter.physicalValue_isUsed = true; } } vas_parameter_list.ParameterSet.array[j].Parameter.arrayLen = t; } res->ServiceParameterList.ParameterSet = vas_parameter_list.ParameterSet; res->ServiceParameterList_isUsed = true; break; } } } } service_id_found = (req->ServiceID == V2G_SERVICE_ID_CHARGING) ? true : service_id_found; if (false == service_id_found) { res->ResponseCode = iso2_responseCodeType_FAILED_ServiceIDInvalid; // [V2G2-464] } /* Check the current response code and check if no external error has occurred */ next_event = (v2g_event)iso_validate_response_code(&res->ResponseCode, conn); /* Set next expected req msg */ conn->ctx->state = (int)iso_dc_state_id::WAIT_FOR_SVCDETAIL_PAYMENTSVCSEL; // [V2G-DC-548] return next_event; } /*! * \brief handle_iso_payment_service_selection This function handles the iso_payment_service_selection msg pair. It * analyzes the request msg and fills the response msg. The request and response msg based on the open V2G structures. * This structures must be provided within the \c conn structure. * \param conn holds the structure with the V2G msg pair. * \return Returns the next V2G-event. */ static enum v2g_event handle_iso_payment_service_selection(struct v2g_connection* conn) { struct iso2_PaymentServiceSelectionReqType* req = &conn->exi_in.iso2EXIDocument->V2G_Message.Body.PaymentServiceSelectionReq; struct iso2_PaymentServiceSelectionResType* res = &conn->exi_out.iso2EXIDocument->V2G_Message.Body.PaymentServiceSelectionRes; enum v2g_event next_event = V2G_EVENT_NO_EVENT; uint8_t idx = 0; bool list_element_found = false; /* At first, publish the received ev request message to the customer mqtt interface */ publish_iso_payment_service_selection_req(req); res->ResponseCode = iso2_responseCodeType_OK; /* check whether the selected payment option was announced at all; * this also covers the case that the peer sends any invalid/unknown payment option * in the message; if we are not happy -> bail out */ for (idx = 0; idx < conn->ctx->evse_v2g_data.payment_option_list.size(); idx++) { if (conn->ctx->evse_v2g_data.payment_option_list.at(idx) == req->SelectedPaymentOption) { list_element_found = true; if (req->SelectedPaymentOption == iso2_paymentOptionType_ExternalPayment) { conn->ctx->p_charger->publish_selected_payment_option(types::iso15118::PaymentOption::ExternalPayment); } else if (req->SelectedPaymentOption == iso2_paymentOptionType_Contract) { conn->ctx->p_charger->publish_selected_payment_option(types::iso15118::PaymentOption::Contract); } break; } } res->ResponseCode = (list_element_found == true) ? res->ResponseCode : iso2_responseCodeType_FAILED_PaymentSelectionInvalid; // [V2G2-465] /* Check the selected services */ bool charge_service_found = false; bool selected_services_found = true; std::map> services_by_provider; for (uint8_t req_idx = 0; (req_idx < req->SelectedServiceList.SelectedService.arrayLen) && (selected_services_found == true); req_idx++) { /* Check if it's a charging service */ if (req->SelectedServiceList.SelectedService.array[req_idx].ServiceID == V2G_SERVICE_ID_CHARGING) { charge_service_found = true; } /* Otherwise check if the selected service is in the stored in the service list */ else { bool entry_found = false; for (uint8_t ci_idx = 0; (ci_idx < conn->ctx->evse_v2g_data.evse_service_list.size()) && (entry_found == false); ci_idx++) { const auto& selected_service = req->SelectedServiceList.SelectedService.array[req_idx]; if (selected_service.ServiceID == conn->ctx->evse_v2g_data.evse_service_list[ci_idx].ServiceID) { /* If it's stored, search for the next requested SelectedService entry */ dlog(DLOG_LEVEL_INFO, "Selected service id %i found", conn->ctx->evse_v2g_data.evse_service_list[ci_idx].ServiceID); if (conn->ctx->evse_v2g_data.evse_service_list[ci_idx].ServiceID == SAE_V2H) { conn->ctx->evse_v2g_data.sae_bidi_data.enabled_sae_v2h = true; conn->ctx->evse_v2g_data.sae_bidi_data.enabled_sae_v2g = false; conn->ctx->p_charger->publish_sae_bidi_mode_active(nullptr); } else if (conn->ctx->evse_v2g_data.evse_service_list[ci_idx].ServiceID == SAE_V2G) { conn->ctx->evse_v2g_data.sae_bidi_data.enabled_sae_v2h = false; conn->ctx->evse_v2g_data.sae_bidi_data.enabled_sae_v2g = true; conn->ctx->p_charger->publish_sae_bidi_mode_active(nullptr); } for (size_t i = 0; i < conn->ctx->supported_vas_services_per_provider.size(); i++) { const auto& provider_services = conn->ctx->supported_vas_services_per_provider.at(i); if (std::find(provider_services.begin(), provider_services.end(), selected_service.ServiceID) != provider_services.end()) { // TODO(SL): What to do if parameterset is not available? types::iso15118_vas::SelectedService service{ selected_service.ServiceID, selected_service.ParameterSetID_isUsed ? selected_service.ParameterSetID : 0}; services_by_provider[i].push_back(service); } } entry_found = true; break; } } if (entry_found == false) { /* If the requested SelectedService entry was not found, break up service list check */ selected_services_found = false; break; } } } if (selected_services_found) { for (const auto& [provider_index, services] : services_by_provider) { conn->ctx->r_vas.at(provider_index)->call_selected_services(services); } } res->ResponseCode = (selected_services_found == false) ? iso2_responseCodeType_FAILED_ServiceSelectionInvalid : res->ResponseCode; // [V2G2-467] res->ResponseCode = (charge_service_found == false) ? iso2_responseCodeType_FAILED_NoChargeServiceSelected : res->ResponseCode; // [V2G2-804] /* Check the current response code and check if no external error has occurred */ next_event = (v2g_event)iso_validate_response_code(&res->ResponseCode, conn); if (req->SelectedPaymentOption == iso2_paymentOptionType_Contract) { dlog(DLOG_LEVEL_INFO, "SelectedPaymentOption: Contract"); conn->ctx->session.iso_selected_payment_option = iso2_paymentOptionType_Contract; /* Set next expected req msg */ conn->ctx->state = (int)iso_dc_state_id::WAIT_FOR_PAYMENTDETAILS_CERTINST_CERTUPD; // [V2G-551] (iso specification describes // only the ac case... ) } else { dlog(DLOG_LEVEL_INFO, "SelectedPaymentOption: ExternalPayment"); conn->ctx->evse_v2g_data.evse_processing[PHASE_AUTH] = (uint8_t)iso2_EVSEProcessingType_Ongoing_WaitingForCustomerInteraction; // [V2G2-854] /* Set next expected req msg */ conn->ctx->state = (int) iso_dc_state_id::WAIT_FOR_AUTHORIZATION; // [V2G-551] (iso specification describes only the ac case... ) conn->ctx->session.auth_start_timeout = getmonotonictime(); } return next_event; } /*! * \brief handle_iso_payment_details This function handles the iso_payment_details msg pair. It analyzes the request msg * and fills the response msg. The request and response msg based on the open V2G structures. This structures must be * provided within the \c conn structure. * \param conn holds the structure with the V2G msg pair. * \return Returns the next V2G-event. */ static enum v2g_event handle_iso_payment_details(struct v2g_connection* conn) { struct iso2_PaymentDetailsReqType* req = &conn->exi_in.iso2EXIDocument->V2G_Message.Body.PaymentDetailsReq; struct iso2_PaymentDetailsResType* res = &conn->exi_out.iso2EXIDocument->V2G_Message.Body.PaymentDetailsRes; enum v2g_event nextEvent = V2G_EVENT_NO_EVENT; int err; // === For the contract certificate, the certificate chain should be checked === if (conn->ctx->session.iso_selected_payment_option == iso2_paymentOptionType_Contract) { // Free old stuff if it exists free_connection_crypto_data(conn); // Parse contract leaf certificate certificate_ptr contract_crt{nullptr, nullptr}; certificate_list chain{}; if (req->ContractSignatureCertChain.Certificate.bytesLen != 0) { err = parse_contract_certificate(contract_crt, req->ContractSignatureCertChain.Certificate.bytes, req->ContractSignatureCertChain.Certificate.bytesLen); } else { dlog(DLOG_LEVEL_ERROR, "No certificate received!"); res->ResponseCode = iso2_responseCodeType_FAILED_CertChainError; goto error_out; } auto cert_emaid = getEmaidFromContractCert(contract_crt); std::string req_emaid{&req->eMAID.characters[0], req->eMAID.charactersLen}; /* Filter '-' character */ cert_emaid.erase(std::remove(cert_emaid.begin(), cert_emaid.end(), '-'), cert_emaid.end()); req_emaid.erase(std::remove(req_emaid.begin(), req_emaid.end(), '-'), req_emaid.end()); dlog(DLOG_LEVEL_TRACE, "emaid-v2g: %s emaid-cert: %s", req_emaid.c_str(), cert_emaid.c_str()); if ((req_emaid.size() != cert_emaid.size()) || (strncasecmp(req_emaid.c_str(), cert_emaid.c_str(), req_emaid.size()) != 0)) { dlog(DLOG_LEVEL_ERROR, "emaid of the contract certificate doesn't match with the received v2g-emaid"); res->ResponseCode = iso2_responseCodeType_FAILED_CertChainError; goto error_out; } if (err != 0) { memset(res, 0, sizeof(*res)); res->ResponseCode = iso2_responseCodeType_FAILED_CertChainError; goto error_out; } assert(conn->pubkey != nullptr); *conn->pubkey = certificate_public_key(contract_crt.get()); err = (*conn->pubkey == nullptr) ? -1 : 0; if (err != 0) { memset(res, 0, sizeof(*res)); res->ResponseCode = iso2_responseCodeType_FAILED_CertChainError; goto error_out; } // Parse contract sub certificates if (req->ContractSignatureCertChain.SubCertificates_isUsed == 1) { for (int i = 0; i < req->ContractSignatureCertChain.SubCertificates.Certificate.arrayLen; i++) { err = load_certificate(&chain, req->ContractSignatureCertChain.SubCertificates.Certificate.array[i].bytes, req->ContractSignatureCertChain.SubCertificates.Certificate.array[i].bytesLen); if (err != 0) { res->ResponseCode = iso2_responseCodeType_FAILED_CertChainError; goto error_out; } } } // initialize contract cert chain to retrieve ocsp request data // Save the certificate chain in a variable in PEM format to publish it std::string contract_cert_chain_pem = chain_to_pem(contract_crt, &chain); std::optional> iso15118_certificate_hash_data; /* Only if certificate chain verification should be done locally by the EVSE */ if (conn->ctx->session.verify_contract_cert_chain == true) { std::string v2g_root_cert_path = conn->ctx->r_security->call_get_verify_file(types::evse_security::CaCertificateType::V2G); std::string mo_root_cert_path = conn->ctx->r_security->call_get_verify_file(types::evse_security::CaCertificateType::MO); crypto::verify_result_t vRes = verify_certificate(contract_crt, &chain, v2g_root_cert_path.c_str(), mo_root_cert_path.c_str(), conn->ctx->debugMode); err = -1; bool forward_contract = false; switch (vRes) { case crypto::verify_result_t::Verified: err = 0; break; case crypto::verify_result_t::CertificateExpired: res->ResponseCode = iso2_responseCodeType_FAILED_CertificateExpired; break; case crypto::verify_result_t::CertificateRevoked: // forward to csms if central_contract_validation_allowed is true if (conn->ctx->evse_v2g_data.central_contract_validation_allowed) { forward_contract = true; } else { res->ResponseCode = iso2_responseCodeType_FAILED_CertificateRevoked; } break; case crypto::verify_result_t::NoCertificateAvailable: // forward to csms if central_contract_validation_allowed is true if (conn->ctx->evse_v2g_data.central_contract_validation_allowed) { forward_contract = true; } else { res->ResponseCode = iso2_responseCodeType_FAILED_NoCertificateAvailable; } break; case crypto::verify_result_t::CertificateNotAllowed: // forward to csms if central_contract_validation_allowed is true if (conn->ctx->evse_v2g_data.central_contract_validation_allowed) { forward_contract = true; } else { res->ResponseCode = iso2_responseCodeType_FAILED_CertificateNotAllowedAtThisEVSE; } break; case crypto::verify_result_t::CertChainError: default: res->ResponseCode = iso2_responseCodeType_FAILED_CertChainError; break; } if (err == -1) { dlog(DLOG_LEVEL_ERROR, "Validation of the contract certificate failed!"); if (!forward_contract) { dlog(DLOG_LEVEL_ERROR, "Central contract validation is not allowed."); // EVSETimeStamp and GenChallenge are mandatory, GenChallenge has fixed size res->EVSETimeStamp = time(NULL); memset(res->GenChallenge.bytes, 0, GEN_CHALLENGE_SIZE); res->GenChallenge.bytesLen = GEN_CHALLENGE_SIZE; goto error_out; } else { dlog(DLOG_LEVEL_INFO, "Central contract validation is allowed: Forwarding contract"); } } else { dlog(DLOG_LEVEL_INFO, "Validation of the contract certificate was successful!"); // contract chain ocsp data can only be retrieved if the MO root is present and the chain could be // verified const auto ocsp_response = conn->ctx->r_security->call_get_mo_ocsp_request_data(contract_cert_chain_pem); iso15118_certificate_hash_data = convert_to_certificate_hash_data_info_vector(ocsp_response); } } generate_random_data(&conn->ctx->session.gen_challenge, GEN_CHALLENGE_SIZE); memcpy(res->GenChallenge.bytes, conn->ctx->session.gen_challenge, GEN_CHALLENGE_SIZE); res->GenChallenge.bytesLen = GEN_CHALLENGE_SIZE; // Publish the provided signature certificate chain and eMAID from EVCC // to receive PnC authorization types::authorization::ProvidedIdToken ProvidedIdToken; ProvidedIdToken.id_token = {std::string(cert_emaid), types::authorization::IdTokenType::eMAID}; ProvidedIdToken.authorization_type = types::authorization::AuthorizationType::PlugAndCharge; ProvidedIdToken.iso15118CertificateHashData = iso15118_certificate_hash_data; ProvidedIdToken.certificate = contract_cert_chain_pem; conn->ctx->session.provided_id_token.emplace(ProvidedIdToken); } else { res->ResponseCode = iso2_responseCodeType_FAILED; goto error_out; } res->EVSETimeStamp = time(NULL); res->ResponseCode = iso2_responseCodeType_OK; error_out: /* Check the current response code and check if no external error has occurred */ nextEvent = iso_validate_response_code(&res->ResponseCode, conn); /* Set next expected req msg */ conn->ctx->state = (int)iso_dc_state_id::WAIT_FOR_AUTHORIZATION; // [V2G-560] conn->ctx->session.auth_start_timeout = getmonotonictime(); return nextEvent; } /*! * \brief handle_iso_authorization This function handles the iso_authorization msg pair. It analyzes the request msg and * fills the response msg. The request and response msg based on the open v2g structures. This structures must be * provided within the \c conn structure. * \param conn holds the structure with the v2g msg pair. * \return Returns the next v2g-event. */ static enum v2g_event handle_iso_authorization(struct v2g_connection* conn) { struct iso2_AuthorizationReqType* req = &conn->exi_in.iso2EXIDocument->V2G_Message.Body.AuthorizationReq; struct iso2_AuthorizationResType* res = &conn->exi_out.iso2EXIDocument->V2G_Message.Body.AuthorizationRes; enum v2g_event next_event = V2G_EVENT_NO_EVENT; bool is_payment_option_contract = conn->ctx->session.iso_selected_payment_option == iso2_paymentOptionType_Contract; /* At first, publish the received ev request message to the customer mqtt interface */ publish_iso_authorization_req(req); res->ResponseCode = iso2_responseCodeType_OK; if (conn->ctx->last_v2g_msg != V2G_AUTHORIZATION_MSG && (conn->ctx->session.iso_selected_payment_option == iso2_paymentOptionType_Contract)) { /* [V2G2-684] */ if (req->GenChallenge_isUsed == 0 || req->GenChallenge.bytesLen != 16 // [V2G2-697] The GenChallenge field shall be exactly 128 bits long. || memcmp(req->GenChallenge.bytes, conn->ctx->session.gen_challenge, 16) != 0) { dlog(DLOG_LEVEL_ERROR, "Challenge invalid or not present"); res->ResponseCode = iso2_responseCodeType_FAILED_ChallengeInvalid; // [V2G2-475] goto error_out; } if (conn->exi_in.iso2EXIDocument->V2G_Message.Header.Signature_isUsed == 0) { dlog(DLOG_LEVEL_ERROR, "Missing signature (Signature_isUsed == 0)"); res->ResponseCode = iso2_responseCodeType_FAILED_SignatureError; goto error_out; } /* Validation of the received signature */ struct iso2_exiFragment iso2_fragment; init_iso2_exiFragment(&iso2_fragment); iso2_fragment.AuthorizationReq_isUsed = 1u; memcpy(&iso2_fragment.AuthorizationReq, req, sizeof(*req)); assert(conn->pubkey != nullptr); const bool bSigRes = check_iso2_signature(&conn->exi_in.iso2EXIDocument->V2G_Message.Header.Signature, conn->pubkey->get(), &iso2_fragment); if (!bSigRes) { res->ResponseCode = iso2_responseCodeType_FAILED_SignatureError; goto error_out; } if (conn->ctx->session.provided_id_token.has_value()) { conn->ctx->p_charger->publish_require_auth_pnc(conn->ctx->session.provided_id_token.value()); conn->ctx->session.provided_id_token.reset(); } else { // this should never happen, since the contract certificate is set in handle_iso_payment_details in case // contract is selected dlog(DLOG_LEVEL_ERROR, "No contract certificate could be retrieved!"); res->ResponseCode = iso2_responseCodeType_FAILED; goto error_out; } } res->EVSEProcessing = (iso2_EVSEProcessingType)conn->ctx->evse_v2g_data.evse_processing[PHASE_AUTH]; if (conn->ctx->evse_v2g_data.evse_processing[PHASE_AUTH] != iso2_EVSEProcessingType_Finished) { if (((is_payment_option_contract == false) && (conn->ctx->session.auth_timeout_eim == 0)) || ((is_payment_option_contract == true) && (conn->ctx->session.auth_timeout_pnc == 0))) { dlog(DLOG_LEVEL_DEBUG, "Waiting for authorization forever!"); } else if ((getmonotonictime() - conn->ctx->session.auth_start_timeout) >= 1000 * (is_payment_option_contract ? conn->ctx->session.auth_timeout_pnc : conn->ctx->session.auth_timeout_eim)) { conn->ctx->session.auth_start_timeout = getmonotonictime(); res->ResponseCode = iso2_responseCodeType_FAILED; } } else if (conn->ctx->session.authorization_rejected == true and (conn->ctx->session.iso_selected_payment_option == iso2_paymentOptionType_Contract)) { if (conn->ctx->session.certificate_status == types::authorization::CertificateStatus::CertificateRevoked) { res->ResponseCode = iso2_responseCodeType_FAILED_CertificateRevoked; } else { res->ResponseCode = iso2_responseCodeType_FAILED; } } else if (conn->ctx->session.authorization_rejected == true) { res->ResponseCode = iso2_responseCodeType_FAILED; } error_out: /* Check the current response code and check if no external error has occurred */ next_event = (v2g_event)iso_validate_response_code(&res->ResponseCode, conn); /* Set next expected req msg */ conn->ctx->state = (res->EVSEProcessing == iso2_EVSEProcessingType_Finished) ? (int)iso_dc_state_id::WAIT_FOR_CHARGEPARAMETERDISCOVERY : (int)iso_dc_state_id::WAIT_FOR_AUTHORIZATION; // [V2G-573] (AC) , [V2G-687] (DC) return next_event; } /*! * \brief handle_iso_charge_parameter_discovery This function handles the iso_charge_parameter_discovery msg pair. It * analyzes the request msg and fills the response msg. The request and response msg based on the open V2G structures. * This structures must be provided within the \c conn structure. * \param conn holds the structure with the V2G msg pair. * \return Returns the next V2G-event. */ static enum v2g_event handle_iso_charge_parameter_discovery(struct v2g_connection* conn) { struct iso2_ChargeParameterDiscoveryReqType* req = &conn->exi_in.iso2EXIDocument->V2G_Message.Body.ChargeParameterDiscoveryReq; struct iso2_ChargeParameterDiscoveryResType* res = &conn->exi_out.iso2EXIDocument->V2G_Message.Body.ChargeParameterDiscoveryRes; enum v2g_event next_event = V2G_EVENT_NO_EVENT; /* At first, publish the received ev request message to the MQTT interface */ publish_iso_charge_parameter_discovery_req(conn->ctx, req); /* First, check requested energy transfer mode, because this information is necessary for futher configuration */ res->ResponseCode = iso2_responseCodeType_FAILED_WrongEnergyTransferMode; for (uint8_t idx = 0; idx < conn->ctx->evse_v2g_data.charge_service.SupportedEnergyTransferMode.EnergyTransferMode.arrayLen; idx++) { if (req->RequestedEnergyTransferMode == conn->ctx->evse_v2g_data.charge_service.SupportedEnergyTransferMode.EnergyTransferMode.array[idx]) { res->ResponseCode = iso2_responseCodeType_OK; // [V2G2-476] log_selected_energy_transfer_type((int)req->RequestedEnergyTransferMode); break; } } res->EVSEChargeParameter_isUsed = 0; res->EVSEProcessing = (iso2_EVSEProcessingType)conn->ctx->evse_v2g_data.evse_processing[PHASE_PARAMETER]; int64_t pmax{0}; if (conn->ctx->is_dc_charger == false) { /* Determin max current and nominal voltage */ /* Setup default params (before the departure time overrides) */ float max_current = conn->ctx->basic_config.evse_ac_nominal_current; int64_t voltage = conn->ctx->evse_v2g_data.evse_nominal_voltage.Value * pow(10, conn->ctx->evse_v2g_data.evse_nominal_voltage.Multiplier); /* nominal voltage */ pmax = max_current * voltage * ((req->RequestedEnergyTransferMode == iso2_EnergyTransferModeType_AC_single_phase_core) ? 1 : 3); dlog(DLOG_LEVEL_INFO, "before adjusting for departure time, max_current %f, nom_voltage %d, pmax %d, departure_duration %d", max_current, voltage, pmax, req->AC_EVChargeParameter.DepartureTime); } /* Configure SA-schedules*/ if (res->EVSEProcessing == iso2_EVSEProcessingType_Finished) { /* If processing is finished, configure SASchedule list */ if (conn->ctx->evse_v2g_data.evse_sa_schedule_list_is_used == false) { int64_t departure_time_duration = req->AC_EVChargeParameter.DepartureTime; /* If not configured, configure SA-schedule automatically for AC charging */ if (conn->ctx->is_dc_charger == false) { populate_physical_value(&conn->ctx->evse_v2g_data.evse_sa_schedule_list.SAScheduleTuple.array[0] .PMaxSchedule.PMaxScheduleEntry.array[0] .PMax, pmax, iso2_unitSymbolType_W); } else { conn->ctx->evse_v2g_data.evse_sa_schedule_list.SAScheduleTuple.array[0] .PMaxSchedule.PMaxScheduleEntry.array[0] .PMax = conn->ctx->evse_v2g_data.power_capabilities.max_power; } if (departure_time_duration == 0) { departure_time_duration = SA_SCHEDULE_DURATION; // one day, per spec } conn->ctx->evse_v2g_data.evse_sa_schedule_list.SAScheduleTuple.array[0] .PMaxSchedule.PMaxScheduleEntry.array[0] .RelativeTimeInterval.start = 0; conn->ctx->evse_v2g_data.evse_sa_schedule_list.SAScheduleTuple.array[0] .PMaxSchedule.PMaxScheduleEntry.array[0] .RelativeTimeInterval.duration_isUsed = 1; constexpr auto PAUSE_DURATION = 60 * 30; conn->ctx->evse_v2g_data.evse_sa_schedule_list.SAScheduleTuple.array[0] .PMaxSchedule.PMaxScheduleEntry.array[0] .RelativeTimeInterval.duration = conn->ctx->evse_v2g_data.no_energy_pause == NoEnergyPauseStatus::None ? departure_time_duration : PAUSE_DURATION; conn->ctx->evse_v2g_data.evse_sa_schedule_list.SAScheduleTuple.array[0] .PMaxSchedule.PMaxScheduleEntry.arrayLen = 1; conn->ctx->evse_v2g_data.evse_sa_schedule_list.SAScheduleTuple.arrayLen = 1; } res->SAScheduleList = conn->ctx->evse_v2g_data.evse_sa_schedule_list; res->SAScheduleList_isUsed = (unsigned int)1; // The SECC shall only omit the parameter 'SAScheduleList' in // case EVSEProcessing is set to 'Ongoing'. if ((req->MaxEntriesSAScheduleTuple_isUsed == (unsigned int)1) && (req->MaxEntriesSAScheduleTuple < res->SAScheduleList.SAScheduleTuple.arrayLen)) { dlog(DLOG_LEVEL_WARNING, "EV's max. SA-schedule-tuple entries exceeded"); } } else { res->EVSEProcessing = iso2_EVSEProcessingType_Ongoing; res->SAScheduleList_isUsed = (unsigned int)0; } /* Checking SAScheduleTupleID */ for (uint8_t idx = 0; idx < res->SAScheduleList.SAScheduleTuple.arrayLen; idx++) { if (res->SAScheduleList.SAScheduleTuple.array[idx].SAScheduleTupleID == (uint8_t)0) { dlog(DLOG_LEVEL_WARNING, "Selected SAScheduleTupleID is not ISO15118 conform. The SECC shall use the " "values 1 to 255"); // [V2G2-773] The SECC shall use the values 1 to 255 for the // parameter SAScheduleTupleID. } } res->SASchedules_isUsed = 0; // TODO: For DC charging wait for CP state B , before transmitting of the response ([V2G2-921], [V2G2-922]). CP // state is checked by other module /* reset our internal reminder that renegotiation was requested */ conn->ctx->session.renegotiation_required = false; // Reset renegotiation flag if (conn->ctx->is_dc_charger == false) { /* Configure AC stucture elements */ res->AC_EVSEChargeParameter_isUsed = 1; res->DC_EVSEChargeParameter_isUsed = 0; populate_ac_evse_status(conn->ctx, &res->AC_EVSEChargeParameter.AC_EVSEStatus); /* Max current */ float max_current = conn->ctx->basic_config.evse_ac_nominal_current; populate_physical_value_float(&res->AC_EVSEChargeParameter.EVSEMaxCurrent, max_current, 1, iso2_unitSymbolType_A); /* Nominal voltage */ res->AC_EVSEChargeParameter.EVSENominalVoltage = conn->ctx->evse_v2g_data.evse_nominal_voltage; /* Calculate pmax based on max current, nominal voltage and phase count (which the car has selected above) */ /* Check the SASchedule */ if (res->SAScheduleList_isUsed == (unsigned int)1) { for (uint8_t idx = 0; idx < res->SAScheduleList.SAScheduleTuple.arrayLen; idx++) { for (uint8_t idx2 = 0; idx2 < res->SAScheduleList.SAScheduleTuple.array[idx].PMaxSchedule.PMaxScheduleEntry.arrayLen; idx2++) if ((res->SAScheduleList.SAScheduleTuple.array[idx] .PMaxSchedule.PMaxScheduleEntry.array[idx2] .PMax.Value * pow(10, res->SAScheduleList.SAScheduleTuple.array[idx] .PMaxSchedule.PMaxScheduleEntry.array[idx2] .PMax.Multiplier)) > pmax) { dlog(DLOG_LEVEL_WARNING, "Provided SA-schedule-list doesn't match with the physical value limits"); } } } if (req->DC_EVChargeParameter_isUsed == (unsigned int)1) { res->ResponseCode = iso2_responseCodeType_FAILED_WrongChargeParameter; // [V2G2-477] } } else { if (conn->ctx->evse_v2g_data.sae_bidi_data.enabled_sae_v2h == true) { static bool first_req = true; if (first_req == true) { res->EVSEProcessing = iso2_EVSEProcessingType_Ongoing; first_req = false; } else { // Check if second req message contains neg values // Check if bulk soc is set if (req->DC_EVChargeParameter.BulkSOC_isUsed == 1 && req->DC_EVChargeParameter.EVMaximumCurrentLimit.Value < 0 && req->DC_EVChargeParameter.EVMaximumPowerLimit_isUsed == 1 && req->DC_EVChargeParameter.EVMaximumPowerLimit.Value < 0) { // Save bulk soc for minimal soc to stop conn->ctx->evse_v2g_data.sae_bidi_data.sae_v2h_minimal_soc = req->DC_EVChargeParameter.BulkSOC; } else { res->ResponseCode = iso2_responseCodeType::iso2_responseCodeType_FAILED_WrongEnergyTransferMode; } res->EVSEProcessing = iso2_EVSEProcessingType_Finished; // reset first_req first_req = true; } } /* Configure DC stucture elements */ res->DC_EVSEChargeParameter_isUsed = 1; res->AC_EVSEChargeParameter_isUsed = 0; res->DC_EVSEChargeParameter.DC_EVSEStatus.EVSEIsolationStatus = (iso2_isolationLevelType)conn->ctx->evse_v2g_data.evse_isolation_status; res->DC_EVSEChargeParameter.DC_EVSEStatus.EVSEIsolationStatus_isUsed = conn->ctx->evse_v2g_data.evse_isolation_status_is_used; res->DC_EVSEChargeParameter.DC_EVSEStatus.EVSENotification = (iso2_EVSENotificationType)conn->ctx->evse_v2g_data.evse_notification; res->DC_EVSEChargeParameter.DC_EVSEStatus.EVSEStatusCode = get_emergency_status_code(conn->ctx, PHASE_PARAMETER); res->DC_EVSEChargeParameter.DC_EVSEStatus.NotificationMaxDelay = (uint16_t)conn->ctx->evse_v2g_data.notification_max_delay; res->DC_EVSEChargeParameter.EVSECurrentRegulationTolerance = conn->ctx->evse_v2g_data.evse_current_regulation_tolerance; res->DC_EVSEChargeParameter.EVSECurrentRegulationTolerance_isUsed = conn->ctx->evse_v2g_data.evse_current_regulation_tolerance_is_used; res->DC_EVSEChargeParameter.EVSEEnergyToBeDelivered = conn->ctx->evse_v2g_data.evse_energy_to_be_delivered; res->DC_EVSEChargeParameter.EVSEEnergyToBeDelivered_isUsed = conn->ctx->evse_v2g_data.evse_energy_to_be_delivered_is_used; res->DC_EVSEChargeParameter.EVSEMaximumCurrentLimit = conn->ctx->evse_v2g_data.power_capabilities.max_current; res->DC_EVSEChargeParameter.EVSEMaximumPowerLimit = conn->ctx->evse_v2g_data.power_capabilities.max_power; res->DC_EVSEChargeParameter.EVSEMaximumVoltageLimit = conn->ctx->evse_v2g_data.power_capabilities.max_voltage; res->DC_EVSEChargeParameter.EVSEMinimumCurrentLimit = conn->ctx->evse_v2g_data.power_capabilities.min_current; res->DC_EVSEChargeParameter.EVSEMinimumVoltageLimit = conn->ctx->evse_v2g_data.power_capabilities.min_voltage; res->DC_EVSEChargeParameter.EVSEPeakCurrentRipple = conn->ctx->evse_v2g_data.evse_peak_current_ripple; if ((unsigned int)1 == req->AC_EVChargeParameter_isUsed) { res->ResponseCode = iso2_responseCodeType_FAILED_WrongChargeParameter; // [V2G2-477] } if (not conn->ctx->evse_v2g_data.sae_bidi_data.enabled_sae_v2h and (req->DC_EVChargeParameter.EVMaximumCurrentLimit.Value < 0 or req->DC_EVChargeParameter.EVMaximumPowerLimit.Value < 0 or req->DC_EVChargeParameter.EVMaximumVoltageLimit.Value < 0)) { res->ResponseCode = iso2_responseCodeType_FAILED_WrongChargeParameter; // [V2G2-477] } constexpr auto physical_value_to_float = [](const iso2_PhysicalValueType& pv) { return calc_physical_value(pv.Value, pv.Multiplier); }; const auto ev_maximum_current_limit = physical_value_to_float(req->DC_EVChargeParameter.EVMaximumCurrentLimit); const auto ev_maximum_voltage_limit = physical_value_to_float(req->DC_EVChargeParameter.EVMaximumVoltageLimit); const auto evse_minimum_current_limit = physical_value_to_float(conn->ctx->evse_v2g_data.power_capabilities.min_current); const auto evse_minimum_voltage_limit = physical_value_to_float(conn->ctx->evse_v2g_data.power_capabilities.min_voltage); if (ev_maximum_current_limit <= evse_minimum_current_limit || ev_maximum_voltage_limit <= evse_minimum_voltage_limit) { res->ResponseCode = iso2_responseCodeType_FAILED_WrongChargeParameter; res->DC_EVSEChargeParameter.DC_EVSEStatus.EVSEStatusCode = iso2_DC_EVSEStatusCodeType_EVSE_Shutdown; } if (res->EVSEProcessing == iso2_EVSEProcessingType_Finished and conn->ctx->evse_v2g_data.no_energy_pause != NoEnergyPauseStatus::None) { res->DC_EVSEChargeParameter.DC_EVSEStatus.EVSENotification = iso2_EVSENotificationType_StopCharging; constexpr auto PAUSE_NOTIFICATION_DELAY = 300; res->DC_EVSEChargeParameter.DC_EVSEStatus.NotificationMaxDelay = conn->ctx->evse_v2g_data.no_energy_pause == NoEnergyPauseStatus::BeforeCableCheck ? 0 : PAUSE_NOTIFICATION_DELAY; } } /* Check the current response code and check if no external error has occurred */ next_event = (v2g_event)iso_validate_response_code(&res->ResponseCode, conn); /* Set next expected req msg */ if (conn->ctx->is_dc_charger == true) { if (conn->ctx->evse_v2g_data.no_energy_pause == NoEnergyPauseStatus::BeforeCableCheck) { conn->ctx->state = (int)iso_dc_state_id::WAIT_FOR_PRECHARGE_POWERDELIVERY; // IEC6185-1:2023 CC.3.5.2 } else { conn->ctx->state = (iso2_EVSEProcessingType_Finished == res->EVSEProcessing) ? (int)iso_dc_state_id::WAIT_FOR_CABLECHECK : (int)iso_dc_state_id::WAIT_FOR_CHARGEPARAMETERDISCOVERY; // [V2G-582], [V2G-688] } } else { conn->ctx->state = (iso2_EVSEProcessingType_Finished == res->EVSEProcessing) ? (int)iso_ac_state_id::WAIT_FOR_POWERDELIVERY : (int)iso_ac_state_id::WAIT_FOR_CHARGEPARAMETERDISCOVERY; } if (res->ResponseCode >= iso2_responseCodeType_FAILED) { res->DC_EVSEChargeParameter.EVSECurrentRegulationTolerance_isUsed = 0; res->DC_EVSEChargeParameter.EVSEEnergyToBeDelivered_isUsed = 0; res->DC_EVSEChargeParameter.DC_EVSEStatus.EVSEIsolationStatus_isUsed = 0; res->SAScheduleList_isUsed = 0; } return next_event; } /*! * \brief handle_iso_power_delivery This function handles the iso_power_delivery msg pair. It analyzes the request msg * and fills the response msg. The request and response msg based on the open V2G structures. This structures must be * provided within the \c conn structure. * \param conn holds the structure with the V2G msg pair. * \return Returns the next V2G-event. */ static enum v2g_event handle_iso_power_delivery(struct v2g_connection* conn) { struct iso2_PowerDeliveryReqType* req = &conn->exi_in.iso2EXIDocument->V2G_Message.Body.PowerDeliveryReq; struct iso2_PowerDeliveryResType* res = &conn->exi_out.iso2EXIDocument->V2G_Message.Body.PowerDeliveryRes; struct timespec ts_abs_timeout; uint8_t sa_schedule_tuple_idx = 0; bool entry_found = false; enum v2g_event next_event = V2G_EVENT_NO_EVENT; /* At first, publish the received EV request message to the MQTT interface */ publish_iso_power_delivery_req(conn->ctx, req); const auto ev_should_pause = conn->ctx->evse_v2g_data.no_energy_pause == NoEnergyPauseStatus::AfterCableCheckPreCharge or conn->ctx->evse_v2g_data.no_energy_pause == NoEnergyPauseStatus::BeforeCableCheck; /* build up response */ res->ResponseCode = iso2_responseCodeType_OK; switch (req->ChargeProgress) { case iso2_chargeProgressType_Start: conn->ctx->p_charger->publish_v2g_setup_finished(nullptr); if (conn->ctx->is_dc_charger == false) { // TODO: For AC charging wait for CP state C or D , before transmitting of the response. CP state is checked // by other module if (conn->ctx->contactor_is_closed == false) { // TODO: Signal closing contactor with MQTT if no timeout while waiting for state C or D conn->ctx->p_charger->publish_ac_close_contactor(nullptr); conn->ctx->session.is_charging = true; /* determine timeout for contactor */ clock_gettime(CLOCK_MONOTONIC, &ts_abs_timeout); timespec_add_ms(&ts_abs_timeout, V2G_CONTACTOR_CLOSE_TIMEOUT); /* wait for contactor to really close or timeout */ dlog(DLOG_LEVEL_INFO, "Waiting for contactor is closed"); int rv = 0; while ((rv == 0) && (conn->ctx->contactor_is_closed == false) && (conn->ctx->intl_emergency_shutdown == false) && (conn->ctx->stop_hlc == false) && (conn->ctx->is_connection_terminated == false)) { pthread_mutex_lock(&conn->ctx->mqtt_lock); rv = pthread_cond_timedwait(&conn->ctx->mqtt_cond, &conn->ctx->mqtt_lock, &ts_abs_timeout); if (rv == EINTR) rv = 0; /* restart */ if (rv == ETIMEDOUT) { dlog(DLOG_LEVEL_ERROR, "timeout while waiting for contactor to close, signaling error"); res->ResponseCode = iso2_responseCodeType_FAILED_ContactorError; } pthread_mutex_unlock(&conn->ctx->mqtt_lock); } } } else if (ev_should_pause) { dlog(DLOG_LEVEL_ERROR, "The EV did not pause the session even EVSE signaled the EV that no energy is " "available. Abort the session"); res->ResponseCode = iso2_responseCodeType_FAILED; conn->ctx->session.is_charging = false; conn->ctx->p_charger->publish_dc_open_contactor(nullptr); } break; case iso2_chargeProgressType_Stop: conn->ctx->session.is_charging = false; if (conn->ctx->is_dc_charger == false) { // TODO: For AC charging wait for CP state change from C/D to B , before transmitting of the response. CP // state is checked by other module conn->ctx->p_charger->publish_ac_open_contactor(nullptr); } else { conn->ctx->p_charger->publish_current_demand_finished(nullptr); conn->ctx->p_charger->publish_dc_open_contactor(nullptr); } break; case iso2_chargeProgressType_Renegotiate: conn->ctx->session.renegotiation_required = true; break; default: dlog(DLOG_LEVEL_ERROR, "Unknown ChargeProgress %d received, signaling error", req->ChargeProgress); res->ResponseCode = iso2_responseCodeType_FAILED; } if (conn->ctx->is_dc_charger == false) { res->AC_EVSEStatus_isUsed = 1; res->DC_EVSEStatus_isUsed = 0; populate_ac_evse_status(conn->ctx, &res->AC_EVSEStatus); } else { res->DC_EVSEStatus_isUsed = 1; res->AC_EVSEStatus_isUsed = 0; res->DC_EVSEStatus.EVSEIsolationStatus = (iso2_isolationLevelType)conn->ctx->evse_v2g_data.evse_isolation_status; res->DC_EVSEStatus.EVSEIsolationStatus_isUsed = conn->ctx->evse_v2g_data.evse_isolation_status_is_used; res->DC_EVSEStatus.EVSENotification = static_cast(conn->ctx->evse_v2g_data.evse_notification); res->DC_EVSEStatus.EVSEStatusCode = get_emergency_status_code(conn->ctx, PHASE_CHARGE); res->DC_EVSEStatus.NotificationMaxDelay = (uint16_t)conn->ctx->evse_v2g_data.notification_max_delay; res->ResponseCode = (req->ChargeProgress == iso2_chargeProgressType_Start) && (res->DC_EVSEStatus.EVSEStatusCode != iso2_DC_EVSEStatusCodeType_EVSE_Ready) ? iso2_responseCodeType_FAILED_PowerDeliveryNotApplied : res->ResponseCode; // [V2G2-480] } res->EVSEStatus_isUsed = 0; /* Check the selected SAScheduleTupleID */ for (sa_schedule_tuple_idx = 0; sa_schedule_tuple_idx < conn->ctx->evse_v2g_data.evse_sa_schedule_list.SAScheduleTuple.arrayLen; sa_schedule_tuple_idx++) { if (conn->ctx->evse_v2g_data.evse_sa_schedule_list.SAScheduleTuple.array[sa_schedule_tuple_idx] .SAScheduleTupleID == req->SAScheduleTupleID) { entry_found = true; conn->ctx->session.sa_schedule_tuple_id = req->SAScheduleTupleID; break; } } res->ResponseCode = (entry_found == false) ? iso2_responseCodeType_FAILED_TariffSelectionInvalid : res->ResponseCode; // [V2G2-479] /* Check EV charging profile values [V2G2-478] */ check_iso2_charging_profile_values(req, res, conn, sa_schedule_tuple_idx); const auto last_v2g_msg = conn->ctx->last_v2g_msg; /* abort charging session if EV is ready to charge after current demand phase */ if ((req->ChargeProgress == iso2_chargeProgressType_Start and (last_v2g_msg == V2G_CURRENT_DEMAND_MSG or last_v2g_msg == V2G_CHARGING_STATUS_MSG)) or (req->ChargeProgress == iso2_chargeProgressType_Renegotiate and (last_v2g_msg != V2G_CURRENT_DEMAND_MSG and last_v2g_msg != V2G_CHARGING_STATUS_MSG))) { res->ResponseCode = iso2_responseCodeType_FAILED; // (/*[V2G2-812]*/ } /* Check the current response code and check if no external error has occurred */ next_event = (v2g_event)iso_validate_response_code(&res->ResponseCode, conn); /* Set next expected req msg */ if ((req->ChargeProgress == iso2_chargeProgressType_Renegotiate) && ((conn->ctx->last_v2g_msg == V2G_CURRENT_DEMAND_MSG) || (conn->ctx->last_v2g_msg == V2G_CHARGING_STATUS_MSG))) { conn->ctx->state = (int)iso_dc_state_id::WAIT_FOR_CHARGEPARAMETERDISCOVERY; // [V2G-813] if (conn->ctx->is_dc_charger == false) { // Reset AC relevant parameter to start the renegotation process conn->ctx->evse_v2g_data.evse_notification = (conn->ctx->evse_v2g_data.evse_notification == iso2_EVSENotificationType_ReNegotiation) ? iso2_EVSENotificationType_None : conn->ctx->evse_v2g_data.evse_notification; } else { // Reset DC relevant parameter to start the renegotation process conn->ctx->evse_v2g_data.evse_processing[PHASE_ISOLATION] = iso2_EVSEProcessingType_Ongoing; conn->ctx->evse_v2g_data.evse_notification = (iso2_EVSENotificationType_ReNegotiation == conn->ctx->evse_v2g_data.evse_notification) ? iso2_EVSENotificationType_None : conn->ctx->evse_v2g_data.evse_notification; conn->ctx->evse_v2g_data.evse_isolation_status = iso2_isolationLevelType_Invalid; } } else if ((req->ChargeProgress == iso2_chargeProgressType_Start) && (conn->ctx->last_v2g_msg != V2G_CURRENT_DEMAND_MSG) && (conn->ctx->last_v2g_msg != V2G_CHARGING_STATUS_MSG)) { conn->ctx->state = (conn->ctx->is_dc_charger == true) ? (int)iso_dc_state_id::WAIT_FOR_CURRENTDEMAND : (int)iso_ac_state_id::WAIT_FOR_CHARGINGSTATUS; // [V2G-590], [V2G2-576] } else { conn->ctx->state = (conn->ctx->is_dc_charger == true) ? (int)iso_dc_state_id::WAIT_FOR_WELDINGDETECTION_SESSIONSTOP : (int)iso_ac_state_id::WAIT_FOR_SESSIONSTOP; // [V2G-601], [V2G2-568] } if (res->ResponseCode >= iso2_responseCodeType_FAILED) { res->DC_EVSEStatus.EVSEIsolationStatus_isUsed = 0; } return next_event; } /*! * \brief handle_iso_charging_status This function handles the iso_charging_status msg pair. It analyzes the request msg * and fills the response msg. The request and response msg based on the open V2G structures. This structures must be * provided within the \c conn structure. * \param conn holds the structure with the V2G msg pair. * \return Returns the next V2G-event. */ static enum v2g_event handle_iso_charging_status(struct v2g_connection* conn) { struct iso2_ChargingStatusResType* res = &conn->exi_out.iso2EXIDocument->V2G_Message.Body.ChargingStatusRes; enum v2g_event next_event = V2G_EVENT_NO_EVENT; /* build up response */ res->ResponseCode = iso2_responseCodeType_OK; res->ReceiptRequired = false; // [V2G2-691] ReceiptRequired shall be false in case of EIM if (conn->ctx->session.iso_selected_payment_option == iso2_paymentOptionType_Contract) { res->ReceiptRequired = conn->ctx->evse_v2g_data.receipt_required; } res->ReceiptRequired_isUsed = true; // [V2G2-691] ChargingStatusRes shall always include ReceiptRequired if (conn->ctx->meter_info.meter_info_is_used == true) { res->MeterInfo.MeterID.charactersLen = conn->ctx->meter_info.meter_id.bytesLen; memcpy(res->MeterInfo.MeterID.characters, conn->ctx->meter_info.meter_id.bytes, iso2_MeterID_CHARACTER_SIZE); res->MeterInfo.MeterReading = conn->ctx->meter_info.meter_reading; res->MeterInfo.MeterReading_isUsed = 1; res->MeterInfo_isUsed = 1; // Reset the signal for the next time handle_set_MeterInfo is signaled conn->ctx->meter_info.meter_info_is_used = false; } else { res->MeterInfo_isUsed = 0; } res->EVSEMaxCurrent_isUsed = (conn->ctx->session.iso_selected_payment_option == iso2_paymentOptionType_Contract) ? (unsigned int)0 : (unsigned int)1; // This element is not included in the message if any AC PnC // Message Set has been selected. if ((unsigned int)1 == res->EVSEMaxCurrent_isUsed) { populate_physical_value_float(&res->EVSEMaxCurrent, conn->ctx->basic_config.evse_ac_current_limit, 1, iso2_unitSymbolType_A); } conn->exi_out.iso2EXIDocument->V2G_Message.Body.ChargingStatusRes_isUsed = 1; /* the following field can also be set in error path */ res->EVSEID.charactersLen = conn->ctx->evse_v2g_data.evse_id.bytesLen; memcpy(res->EVSEID.characters, conn->ctx->evse_v2g_data.evse_id.bytes, conn->ctx->evse_v2g_data.evse_id.bytesLen); /* in error path the session might not be available */ res->SAScheduleTupleID = conn->ctx->session.sa_schedule_tuple_id; populate_ac_evse_status(conn->ctx, &res->AC_EVSEStatus); /* Check the current response code and check if no external error has occurred */ next_event = (enum v2g_event)iso_validate_response_code(&res->ResponseCode, conn); /* Set next expected req msg */ conn->ctx->state = (((int)1 == res->ReceiptRequired)) ? (int)iso_ac_state_id::WAIT_FOR_METERINGRECEIPT : (int)iso_ac_state_id::WAIT_FOR_CHARGINGSTATUS_POWERDELIVERY; // [V2G2-577], [V2G2-575] return next_event; } /*! * \brief handle_iso_metering_receipt This function handles the iso_metering_receipt msg pair. It analyzes the request * msg and fills the response msg. The request and response msg based on the open V2G structures. This structures must * be provided within the \c conn structure. \param conn holds the structure with the V2G msg pair. \return Returns the * next V2G-event. */ static enum v2g_event handle_iso_metering_receipt(struct v2g_connection* conn) { struct iso2_MeteringReceiptReqType* req = &conn->exi_in.iso2EXIDocument->V2G_Message.Body.MeteringReceiptReq; struct iso2_MeteringReceiptResType* res = &conn->exi_out.iso2EXIDocument->V2G_Message.Body.MeteringReceiptRes; enum v2g_event next_event = V2G_EVENT_NO_EVENT; /* At first, publish the received ev request message to the MQTTinterface */ publish_iso_metering_receipt_req(req); dlog(DLOG_LEVEL_TRACE, "EVSE side: meteringReceipt called"); dlog(DLOG_LEVEL_TRACE, "\tReceived data:"); dlog(DLOG_LEVEL_TRACE, "\t\t ID=%c%c%c", req->Id.characters[0], req->Id.characters[1], req->Id.characters[2]); dlog(DLOG_LEVEL_TRACE, "\t\t SAScheduleTupleID=%d", req->SAScheduleTupleID); dlog(DLOG_LEVEL_TRACE, "\t\t SessionID=%d", req->SessionID.bytes[1]); dlog(DLOG_LEVEL_TRACE, "\t\t MeterInfo.MeterStatus=%d", req->MeterInfo.MeterStatus); dlog(DLOG_LEVEL_TRACE, "\t\t MeterInfo.MeterID=%d", req->MeterInfo.MeterID.characters[0]); dlog(DLOG_LEVEL_TRACE, "\t\t MeterInfo.isused.MeterReading=%d", req->MeterInfo.MeterReading_isUsed); dlog(DLOG_LEVEL_TRACE, "\t\t MeterReading.Value=%lu", (long unsigned int)req->MeterInfo.MeterReading); dlog(DLOG_LEVEL_TRACE, "\t\t MeterInfo.TMeter=%li", (long int)req->MeterInfo.TMeter); res->ResponseCode = iso2_responseCodeType_OK; if (conn->ctx->is_dc_charger == false) { /* for AC charging we respond with AC_EVSEStatus */ res->EVSEStatus_isUsed = 0; res->AC_EVSEStatus_isUsed = 1; res->DC_EVSEStatus_isUsed = 0; populate_ac_evse_status(conn->ctx, &res->AC_EVSEStatus); } else { res->DC_EVSEStatus_isUsed = 1; res->AC_EVSEStatus_isUsed = 0; } /* Check the current response code and check if no external error has occurred */ next_event = (v2g_event)iso_validate_response_code(&res->ResponseCode, conn); /* Set next expected req msg */ conn->ctx->state = (conn->ctx->is_dc_charger == false) ? (int)iso_ac_state_id::WAIT_FOR_CHARGINGSTATUS_POWERDELIVERY : (int)iso_dc_state_id::WAIT_FOR_CURRENTDEMAND_POWERDELIVERY; // [V2G2-580]/[V2G-797] return next_event; } /*! * \brief handle_iso_certificate_update This function handles the iso_certificate_update msg pair. It analyzes the * request msg and fills the response msg. The request and response msg based on the open V2G structures. This * structures must be provided within the \c conn structure. * \param conn holds the structure with the V2G msg pair. * \return Returns the next V2G-event. */ static enum v2g_event handle_iso_certificate_update(struct v2g_connection* conn) { // TODO: implement CertificateUpdate handling return V2G_EVENT_NO_EVENT; } /*! * \brief handle_iso_certificate_installation This function handles the iso_certificate_installation msg pair. It * analyzes the request msg and fills the response msg. The request and response msg based on the open V2G structures. * This structures must be provided within the \c conn structure. * \param conn holds the structure with the V2G msg pair. * \return Returns the next V2G-event. */ static enum v2g_event handle_iso_certificate_installation(struct v2g_connection* conn) { struct iso2_CertificateInstallationResType* res = &conn->exi_out.iso2EXIDocument->V2G_Message.Body.CertificateInstallationRes; enum v2g_event nextEvent = V2G_EVENT_SEND_AND_TERMINATE; struct timespec ts_abs_timeout; int rv = 0; /* At first, publish the received EV request message to the customer MQTT interface */ if (publish_iso_certificate_installation_exi_req(conn->ctx, conn->buffer + V2GTP_HEADER_LENGTH, conn->stream.data_size - V2GTP_HEADER_LENGTH) == false) { dlog(DLOG_LEVEL_ERROR, "Failed to send CertificateInstallationExiReq"); goto exit; } /* Waiting for the CertInstallationExiRes msg */ clock_gettime(CLOCK_MONOTONIC, &ts_abs_timeout); timespec_add_ms(&ts_abs_timeout, V2G_SECC_MSG_CERTINSTALL_TIME); dlog(DLOG_LEVEL_INFO, "Waiting for the CertInstallationExiRes msg"); while ((rv == 0) && (conn->ctx->evse_v2g_data.cert_install_res_b64_buffer.empty() == true) && (conn->ctx->intl_emergency_shutdown == false) && (conn->ctx->stop_hlc == false) && (conn->ctx->is_connection_terminated == false)) { // [V2G2-917] pthread_mutex_lock(&conn->ctx->mqtt_lock); rv = pthread_cond_timedwait(&conn->ctx->mqtt_cond, &conn->ctx->mqtt_lock, &ts_abs_timeout); if (rv == EINTR) rv = 0; /* restart */ if (rv == ETIMEDOUT) { dlog(DLOG_LEVEL_ERROR, "CertificateInstallationRes timeout occurred"); conn->ctx->intl_emergency_shutdown = true; // [V2G2-918] Initiating emergency shutdown, response code faild // will be set in iso_validate_response_code() function } pthread_mutex_unlock(&conn->ctx->mqtt_lock); } if ((conn->ctx->evse_v2g_data.cert_install_res_b64_buffer.empty() == false) && (conn->ctx->evse_v2g_data.cert_install_status == true)) { const auto data = openssl::base64_decode(conn->ctx->evse_v2g_data.cert_install_res_b64_buffer.data(), conn->ctx->evse_v2g_data.cert_install_res_b64_buffer.size()); if (data.empty() || (data.size() > DEFAULT_BUFFER_SIZE - V2GTP_HEADER_LENGTH)) { dlog(DLOG_LEVEL_ERROR, "Failed to decode base64 stream"); goto exit; } else { std::memcpy(conn->buffer + V2GTP_HEADER_LENGTH, data.data(), data.size()); conn->stream.byte_pos = data.size(); } nextEvent = V2G_EVENT_SEND_RECV_EXI_MSG; res->ResponseCode = iso2_responseCodeType_OK; // Is irrelevant but must be valid to serve the internal validation conn->stream.byte_pos += V2GTP_HEADER_LENGTH; // byte_pos had only the payload, so increase it to be header + payload } else { res->ResponseCode = iso2_responseCodeType_FAILED; } exit: if (V2G_EVENT_SEND_RECV_EXI_MSG != nextEvent) { /* Check the current response code and check if no external error has occurred */ nextEvent = (enum v2g_event)iso_validate_response_code(&res->ResponseCode, conn); } else { /* Reset v2g-msg If there, in case of an error */ init_iso2_CertificateInstallationResType(res); } /* Set next expected req msg */ conn->ctx->state = (int)iso_dc_state_id::WAIT_FOR_PAYMENTDETAILS; // [V2G-554] return nextEvent; } /*! * \brief handle_iso_cable_check This function handles the iso_cable_check msg pair. It analyzes the request msg and * fills the response msg. The request and response msg based on the open V2G structures. This structures must be * provided within the \c conn structure. * \param conn holds the structure with the V2G msg pair. * \return Returns the next V2G-event. */ static enum v2g_event handle_iso_cable_check(struct v2g_connection* conn) { struct iso2_CableCheckReqType* req = &conn->exi_in.iso2EXIDocument->V2G_Message.Body.CableCheckReq; struct iso2_CableCheckResType* res = &conn->exi_out.iso2EXIDocument->V2G_Message.Body.CableCheckRes; enum v2g_event next_event = V2G_EVENT_NO_EVENT; /* At first, publish the received EV request message to the MQTT interface */ publish_DcEvStatus(conn->ctx, req->DC_EVStatus); // TODO: For DC charging wait for CP state C or D , before transmitting of the response ([V2G2-917], [V2G2-918]). CP // state is checked by other module /* Fill the CableCheckRes */ res->ResponseCode = iso2_responseCodeType_OK; res->DC_EVSEStatus.EVSEIsolationStatus = (iso2_isolationLevelType)conn->ctx->evse_v2g_data.evse_isolation_status; res->DC_EVSEStatus.EVSEIsolationStatus_isUsed = conn->ctx->evse_v2g_data.evse_isolation_status_is_used; res->DC_EVSEStatus.EVSENotification = static_cast(conn->ctx->evse_v2g_data.evse_notification); res->DC_EVSEStatus.NotificationMaxDelay = (uint16_t)conn->ctx->evse_v2g_data.notification_max_delay; res->EVSEProcessing = static_cast(conn->ctx->evse_v2g_data.evse_processing[PHASE_ISOLATION]); if (conn->ctx->intl_emergency_shutdown == false && res->EVSEProcessing == iso2_EVSEProcessingType_Finished) { res->DC_EVSEStatus.EVSEStatusCode = iso2_DC_EVSEStatusCodeType_EVSE_Ready; } else { res->DC_EVSEStatus.EVSEStatusCode = get_emergency_status_code(conn->ctx, PHASE_ISOLATION); } /* Check the current response code and check if no external error has occurred */ next_event = (v2g_event)iso_validate_response_code(&res->ResponseCode, conn); /* Set next expected req msg */ conn->ctx->state = (res->EVSEProcessing == iso2_EVSEProcessingType_Finished) ? (int)iso_dc_state_id::WAIT_FOR_PRECHARGE : (int)iso_dc_state_id::WAIT_FOR_CABLECHECK; // [V2G-584], [V2G-621] if (res->ResponseCode >= iso2_responseCodeType_FAILED) { res->DC_EVSEStatus.EVSEIsolationStatus_isUsed = 0; } return next_event; } /*! * \brief handle_iso_pre_charge This function handles the iso_pre_charge msg pair. It analyzes the request msg and fills * the response msg. The request and response msg based on the open V2G structures. This structures must be provided * within the \c conn structure. * \param conn holds the structure with the V2G msg pair. * \return Returns the next V2G-event. */ static enum v2g_event handle_iso_pre_charge(struct v2g_connection* conn) { struct iso2_PreChargeReqType* req = &conn->exi_in.iso2EXIDocument->V2G_Message.Body.PreChargeReq; struct iso2_PreChargeResType* res = &conn->exi_out.iso2EXIDocument->V2G_Message.Body.PreChargeRes; enum v2g_event next_event = V2G_EVENT_NO_EVENT; /* At first, publish the received EV request message to the MQTT interface */ publish_iso_pre_charge_req(conn->ctx, req); /* Fill the PreChargeRes*/ res->DC_EVSEStatus.EVSEIsolationStatus = (iso2_isolationLevelType)conn->ctx->evse_v2g_data.evse_isolation_status; res->DC_EVSEStatus.EVSEIsolationStatus_isUsed = conn->ctx->evse_v2g_data.evse_isolation_status_is_used; res->DC_EVSEStatus.EVSENotification = static_cast(conn->ctx->evse_v2g_data.evse_notification); res->DC_EVSEStatus.EVSEStatusCode = get_emergency_status_code(conn->ctx, PHASE_PRECHARGE); res->DC_EVSEStatus.NotificationMaxDelay = (uint16_t)conn->ctx->evse_v2g_data.notification_max_delay; res->EVSEPresentVoltage = (iso2_PhysicalValueType)conn->ctx->evse_v2g_data.evse_present_voltage; res->ResponseCode = iso2_responseCodeType_OK; /* Check the current response code and check if no external error has occurred */ next_event = (v2g_event)iso_validate_response_code(&res->ResponseCode, conn); /* Set next expected req msg */ conn->ctx->state = (int)iso_dc_state_id::WAIT_FOR_PRECHARGE_POWERDELIVERY; // [V2G-587] if (res->ResponseCode >= iso2_responseCodeType_FAILED) { res->DC_EVSEStatus.EVSEIsolationStatus_isUsed = 0; } return next_event; } /*! * \brief handle_iso_current_demand This function handles the iso_current_demand msg pair. It analyzes the request msg * and fills the response msg. The request and response msg based on the open V2G structures. This structures must be * provided within the \c conn structure. * \param conn holds the structure with the V2G msg pair. * \return Returns the next V2G-event. */ static enum v2g_event handle_iso_current_demand(struct v2g_connection* conn) { struct iso2_CurrentDemandReqType* req = &conn->exi_in.iso2EXIDocument->V2G_Message.Body.CurrentDemandReq; struct iso2_CurrentDemandResType* res = &conn->exi_out.iso2EXIDocument->V2G_Message.Body.CurrentDemandRes; enum v2g_event next_event = V2G_EVENT_NO_EVENT; /* At first, publish the received EV request message to the MQTT interface */ publish_iso_current_demand_req(conn->ctx, req); res->DC_EVSEStatus.EVSEIsolationStatus = (iso2_isolationLevelType)conn->ctx->evse_v2g_data.evse_isolation_status; res->DC_EVSEStatus.EVSEIsolationStatus_isUsed = conn->ctx->evse_v2g_data.evse_isolation_status_is_used; res->DC_EVSEStatus.EVSENotification = static_cast(conn->ctx->evse_v2g_data.evse_notification); res->DC_EVSEStatus.EVSEStatusCode = get_emergency_status_code(conn->ctx, PHASE_CHARGE); res->DC_EVSEStatus.NotificationMaxDelay = (uint16_t)conn->ctx->evse_v2g_data.notification_max_delay; if ((conn->ctx->evse_v2g_data.evse_maximum_current_limit_is_used == 1) && (calc_physical_value(req->EVTargetCurrent.Value, req->EVTargetCurrent.Multiplier) >= calc_physical_value(conn->ctx->evse_v2g_data.evse_maximum_current_limit.Value, conn->ctx->evse_v2g_data.evse_maximum_current_limit.Multiplier))) { conn->ctx->evse_v2g_data.evse_current_limit_achieved = (int)1; } else { conn->ctx->evse_v2g_data.evse_current_limit_achieved = (int)0; } res->EVSECurrentLimitAchieved = conn->ctx->evse_v2g_data.evse_current_limit_achieved; memcpy(res->EVSEID.characters, conn->ctx->evse_v2g_data.evse_id.bytes, conn->ctx->evse_v2g_data.evse_id.bytesLen); res->EVSEID.charactersLen = conn->ctx->evse_v2g_data.evse_id.bytesLen; res->EVSEMaximumCurrentLimit = conn->ctx->evse_v2g_data.evse_maximum_current_limit; res->EVSEMaximumCurrentLimit_isUsed = conn->ctx->evse_v2g_data.evse_maximum_current_limit_is_used; res->EVSEMaximumPowerLimit = conn->ctx->evse_v2g_data.evse_maximum_power_limit; res->EVSEMaximumPowerLimit_isUsed = conn->ctx->evse_v2g_data.evse_maximum_power_limit_is_used; res->EVSEMaximumVoltageLimit = conn->ctx->evse_v2g_data.evse_maximum_voltage_limit; res->EVSEMaximumVoltageLimit_isUsed = conn->ctx->evse_v2g_data.evse_maximum_voltage_limit_is_used; double EVTargetPower = calc_physical_value(req->EVTargetCurrent.Value, req->EVTargetCurrent.Multiplier) * calc_physical_value(req->EVTargetVoltage.Value, req->EVTargetVoltage.Multiplier); if ((conn->ctx->evse_v2g_data.evse_maximum_power_limit_is_used == 1) && (EVTargetPower >= calc_physical_value(conn->ctx->evse_v2g_data.evse_maximum_power_limit.Value, conn->ctx->evse_v2g_data.evse_maximum_power_limit.Multiplier))) { conn->ctx->evse_v2g_data.evse_power_limit_achieved = (int)1; } else { conn->ctx->evse_v2g_data.evse_power_limit_achieved = (int)0; } res->EVSEPowerLimitAchieved = conn->ctx->evse_v2g_data.evse_power_limit_achieved; res->EVSEPresentCurrent = conn->ctx->evse_v2g_data.evse_present_current; res->EVSEPresentVoltage = conn->ctx->evse_v2g_data.evse_present_voltage; if ((conn->ctx->evse_v2g_data.evse_maximum_voltage_limit_is_used == 1) && (calc_physical_value(req->EVTargetVoltage.Value, req->EVTargetVoltage.Multiplier) >= calc_physical_value(conn->ctx->evse_v2g_data.evse_maximum_voltage_limit.Value, conn->ctx->evse_v2g_data.evse_maximum_voltage_limit.Multiplier))) { conn->ctx->evse_v2g_data.evse_voltage_limit_achieved = (int)1; } else { conn->ctx->evse_v2g_data.evse_voltage_limit_achieved = (int)0; } res->EVSEVoltageLimitAchieved = conn->ctx->evse_v2g_data.evse_voltage_limit_achieved; if (conn->ctx->meter_info.meter_info_is_used == true) { res->MeterInfo.MeterID.charactersLen = conn->ctx->meter_info.meter_id.bytesLen; memcpy(res->MeterInfo.MeterID.characters, conn->ctx->meter_info.meter_id.bytes, iso2_MeterID_CHARACTER_SIZE); res->MeterInfo.MeterReading = conn->ctx->meter_info.meter_reading; res->MeterInfo.MeterReading_isUsed = 1; res->MeterInfo_isUsed = 1; // Reset the signal for the next time handle_set_MeterInfo is signaled conn->ctx->meter_info.meter_info_is_used = false; } else { res->MeterInfo_isUsed = 0; } res->ReceiptRequired = conn->ctx->evse_v2g_data.receipt_required; // TODO: PNC only res->ReceiptRequired_isUsed = (conn->ctx->session.iso_selected_payment_option == iso2_paymentOptionType_Contract) ? (unsigned int)conn->ctx->evse_v2g_data.receipt_required : (unsigned int)0; res->ResponseCode = iso2_responseCodeType_OK; res->SAScheduleTupleID = conn->ctx->session.sa_schedule_tuple_id; static uint8_t req_pos_value_count = 0; if (conn->ctx->evse_v2g_data.sae_bidi_data.enabled_sae_v2g == true) { // case: evse initiated -> Negative PresentCurrent, EvseMaxCurrentLimit, EvseMaxCurrentLimit if (conn->ctx->evse_v2g_data.sae_bidi_data.discharging == false && conn->ctx->evse_v2g_data.evse_present_current.Value < 0 && conn->ctx->evse_v2g_data.evse_maximum_current_limit_is_used == true && conn->ctx->evse_v2g_data.evse_maximum_current_limit.Value < 0 && conn->ctx->evse_v2g_data.evse_maximum_power_limit_is_used == true && conn->ctx->evse_v2g_data.evse_maximum_power_limit.Value < 0) { if (req->EVTargetCurrent.Value > 0) { if (req_pos_value_count++ >= 1) { dlog(DLOG_LEVEL_WARNING, "SAE V2G Bidi handshake was not recognized by the ev side. Instead of " "shutting down, it is better to wait for a correct response"); req_pos_value_count = 0; } else { req_pos_value_count = 0; conn->ctx->evse_v2g_data.sae_bidi_data.discharging = true; } } } else if (conn->ctx->evse_v2g_data.sae_bidi_data.discharging == true && conn->ctx->evse_v2g_data.evse_present_current.Value > 0 && conn->ctx->evse_v2g_data.evse_maximum_current_limit_is_used == true && conn->ctx->evse_v2g_data.evse_maximum_current_limit.Value > 0 && conn->ctx->evse_v2g_data.evse_maximum_power_limit_is_used == true && conn->ctx->evse_v2g_data.evse_maximum_power_limit.Value > 0) { if (req->EVTargetCurrent.Value < 0) { if (req_pos_value_count++ >= 1) { dlog(DLOG_LEVEL_WARNING, "SAE V2G Bidi handshake was not recognized by the ev side. Instead of " "shutting down, it is better to wait for a correct response"); req_pos_value_count = 0; } else { req_pos_value_count = 0; conn->ctx->evse_v2g_data.sae_bidi_data.discharging = false; } } } // case: ev initiated -> Negative EvTargetCurrent, EVMaxCurrentLimit, EVMaxPowerLimit // Todo(SL): Is it necessary to notify the evse_manager that the ev want to give power/current? // Or is it obvious because of the negative target current request. } else if (conn->ctx->evse_v2g_data.sae_bidi_data.enabled_sae_v2h == true) { if (req->DC_EVStatus.EVRESSSOC <= conn->ctx->evse_v2g_data.sae_bidi_data.sae_v2h_minimal_soc) { res->DC_EVSEStatus.EVSEStatusCode = iso2_DC_EVSEStatusCodeType_EVSE_Shutdown; } } /* Check the current response code and check if no external error has occurred */ next_event = (v2g_event)iso_validate_response_code(&res->ResponseCode, conn); /* Set next expected req msg */ conn->ctx->state = ((res->ReceiptRequired_isUsed == (unsigned int)1) && (res->ReceiptRequired == (int)1)) ? (int)iso_dc_state_id::WAIT_FOR_METERINGRECEIPT : (int)iso_dc_state_id::WAIT_FOR_CURRENTDEMAND_POWERDELIVERY; // [V2G-795], [V2G-593] if (res->ResponseCode >= iso2_responseCodeType_FAILED) { res->DC_EVSEStatus.EVSEIsolationStatus_isUsed = 0; res->MeterInfo_isUsed = 0; res->MeterInfo.MeterReading_isUsed = 0; res->EVSEMaximumVoltageLimit_isUsed = 0; res->EVSEMaximumCurrentLimit_isUsed = 0; res->EVSEMaximumPowerLimit_isUsed = 0; } return next_event; } /*! * \brief handle_iso_welding_detection This function handles the iso_welding_detection msg pair. It analyzes the request * msg and fills the response msg. The request and response msg based on the open V2G structures. This structures must * be provided within the \c conn structure. * \param conn holds the structure with the V2G msg pair. * \return Returns the next V2G-event. */ static enum v2g_event handle_iso_welding_detection(struct v2g_connection* conn) { struct iso2_WeldingDetectionReqType* req = &conn->exi_in.iso2EXIDocument->V2G_Message.Body.WeldingDetectionReq; struct iso2_WeldingDetectionResType* res = &conn->exi_out.iso2EXIDocument->V2G_Message.Body.WeldingDetectionRes; enum v2g_event next_event = V2G_EVENT_NO_EVENT; /* At first, publish the received EV request message to the MQTT interface */ publish_iso_welding_detection_req(conn->ctx, req); // TODO: Wait for CP state B, before transmitting of the response, or signal intl_emergency_shutdown in conn->ctx // ([V2G2-920], [V2G2-921]). res->DC_EVSEStatus.EVSEIsolationStatus = (iso2_isolationLevelType)conn->ctx->evse_v2g_data.evse_isolation_status; res->DC_EVSEStatus.EVSEIsolationStatus_isUsed = conn->ctx->evse_v2g_data.evse_isolation_status_is_used; res->DC_EVSEStatus.EVSENotification = static_cast(conn->ctx->evse_v2g_data.evse_notification); res->DC_EVSEStatus.EVSEStatusCode = get_emergency_status_code(conn->ctx, PHASE_WELDING); res->DC_EVSEStatus.NotificationMaxDelay = (uint16_t)conn->ctx->evse_v2g_data.notification_max_delay; res->EVSEPresentVoltage = conn->ctx->evse_v2g_data.evse_present_voltage; res->ResponseCode = iso2_responseCodeType_OK; /* Check the current response code and check if no external error has occurred */ next_event = (v2g_event)iso_validate_response_code(&res->ResponseCode, conn); /* Set next expected req msg */ conn->ctx->state = (int)iso_dc_state_id::WAIT_FOR_WELDINGDETECTION_SESSIONSTOP; // [V2G-597] if (res->ResponseCode >= iso2_responseCodeType_FAILED) { res->DC_EVSEStatus.EVSEIsolationStatus_isUsed = 0; } return next_event; } /*! * \brief handle_iso_session_stop This function handles the iso_session_stop msg pair. It analyses the request msg and * fills the response msg. The request and response msg based on the open V2G structures. This structures must be * provided within the \c conn structure. * \param conn holds the structure with the V2G msg pair. * \return Returns the next V2G-event. */ static enum v2g_event handle_iso_session_stop(struct v2g_connection* conn) { struct iso2_SessionStopReqType* req = &conn->exi_in.iso2EXIDocument->V2G_Message.Body.SessionStopReq; struct iso2_SessionStopResType* res = &conn->exi_out.iso2EXIDocument->V2G_Message.Body.SessionStopRes; res->ResponseCode = iso2_responseCodeType_OK; /* Check the current response code and check if no external error has occurred */ auto event = iso_validate_response_code(&res->ResponseCode, conn); /* In case of an error, ignore payload of the request message and trigger a d-link error */ if (event != V2G_EVENT_NO_EVENT) { conn->d_link_action = dLinkAction::D_LINK_ACTION_ERROR; conn->ctx->hlc_pause_active = false; /* Set next expected req msg */ conn->ctx->state = static_cast(iso_dc_state_id::WAIT_FOR_TERMINATED_SESSION); return event; } /* Set the next charging state */ switch (req->ChargingSession) { case iso2_chargingSessionType_Terminate: conn->d_link_action = dLinkAction::D_LINK_ACTION_TERMINATE; conn->ctx->hlc_pause_active = false; /* Set next expected req msg */ conn->ctx->state = static_cast(iso_dc_state_id::WAIT_FOR_TERMINATED_SESSION); break; case iso2_chargingSessionType_Pause: /* Set next expected req msg */ /* Check if the EV is allowed to request the sleep mode. TODO: Remove "true" if sleep mode is supported */ if (((conn->ctx->last_v2g_msg != V2G_POWER_DELIVERY_MSG) && (conn->ctx->last_v2g_msg != V2G_WELDING_DETECTION_MSG))) { conn->d_link_action = dLinkAction::D_LINK_ACTION_TERMINATE; res->ResponseCode = iso2_responseCodeType_FAILED; conn->ctx->hlc_pause_active = false; conn->ctx->state = static_cast(iso_dc_state_id::WAIT_FOR_TERMINATED_SESSION); } else { /* Init sleep mode for the EV */ conn->d_link_action = dLinkAction::D_LINK_ACTION_PAUSE; conn->ctx->hlc_pause_active = true; conn->ctx->state = (int)iso_dc_state_id::WAIT_FOR_SESSIONSETUP; } break; default: /* Set next expected req msg */ conn->d_link_action = dLinkAction::D_LINK_ACTION_TERMINATE; conn->ctx->state = static_cast(iso_dc_state_id::WAIT_FOR_TERMINATED_SESSION); } return V2G_EVENT_SEND_AND_TERMINATE; // Charging must be terminated after sending the response message [V2G2-571] } enum v2g_event iso_handle_request(v2g_connection* conn) { struct iso2_exiDocument* exi_in = conn->exi_in.iso2EXIDocument; struct iso2_exiDocument* exi_out = conn->exi_out.iso2EXIDocument; enum v2g_event next_v2g_event = V2G_EVENT_TERMINATE_CONNECTION; /* extract session id */ conn->ctx->ev_v2g_data.received_session_id = v2g_session_id_from_exi(true, exi_in); /* init V2G structure (document, header, body) */ init_iso2_exiDocument(exi_out); init_iso2_MessageHeaderType(&exi_out->V2G_Message.Header); exi_out->V2G_Message.Header.SessionID.bytesLen = 8; init_iso2_BodyType(&exi_out->V2G_Message.Body); /* handle each message type individually; * we use a none-usual source code formatting here to optically group the individual * request a little bit */ if (exi_in->V2G_Message.Body.CurrentDemandReq_isUsed) { dlog(DLOG_LEVEL_TRACE, "Handling CurrentDemandReq"); if (conn->ctx->last_v2g_msg == V2G_POWER_DELIVERY_MSG) { conn->ctx->p_charger->publish_current_demand_started(nullptr); conn->ctx->session.is_charging = true; } conn->ctx->current_v2g_msg = V2G_CURRENT_DEMAND_MSG; exi_out->V2G_Message.Body.CurrentDemandRes_isUsed = 1u; init_iso2_CurrentDemandResType(&exi_out->V2G_Message.Body.CurrentDemandRes); next_v2g_event = handle_iso_current_demand(conn); // [V2G2-592] } else if (exi_in->V2G_Message.Body.SessionSetupReq_isUsed) { dlog(DLOG_LEVEL_TRACE, "Handling SessionSetupReq"); conn->ctx->current_v2g_msg = V2G_SESSION_SETUP_MSG; exi_out->V2G_Message.Body.SessionSetupRes_isUsed = 1u; init_iso2_SessionSetupResType(&exi_out->V2G_Message.Body.SessionSetupRes); next_v2g_event = handle_iso_session_setup(conn); // [V2G2-542] } else if (exi_in->V2G_Message.Body.ServiceDiscoveryReq_isUsed) { dlog(DLOG_LEVEL_TRACE, "Handling ServiceDiscoveryReq"); conn->ctx->current_v2g_msg = V2G_SERVICE_DISCOVERY_MSG; exi_out->V2G_Message.Body.ServiceDiscoveryRes_isUsed = 1u; init_iso2_ServiceDiscoveryResType(&exi_out->V2G_Message.Body.ServiceDiscoveryRes); next_v2g_event = handle_iso_service_discovery(conn); // [V2G2-544] } else if (exi_in->V2G_Message.Body.ServiceDetailReq_isUsed) { dlog(DLOG_LEVEL_TRACE, "Handling ServiceDetailReq"); conn->ctx->current_v2g_msg = V2G_SERVICE_DETAIL_MSG; exi_out->V2G_Message.Body.ServiceDetailRes_isUsed = 1u; init_iso2_ServiceDetailResType(&exi_out->V2G_Message.Body.ServiceDetailRes); next_v2g_event = handle_iso_service_detail(conn); // [V2G2-547] } else if (exi_in->V2G_Message.Body.PaymentServiceSelectionReq_isUsed) { dlog(DLOG_LEVEL_TRACE, "Handling PaymentServiceSelectionReq"); conn->ctx->current_v2g_msg = V2G_PAYMENT_SERVICE_SELECTION_MSG; exi_out->V2G_Message.Body.PaymentServiceSelectionRes_isUsed = 1u; init_iso2_PaymentServiceSelectionResType(&exi_out->V2G_Message.Body.PaymentServiceSelectionRes); next_v2g_event = handle_iso_payment_service_selection(conn); // [V2G2-550] } else if (exi_in->V2G_Message.Body.PaymentDetailsReq_isUsed) { dlog(DLOG_LEVEL_TRACE, "Handling PaymentDetailsReq"); conn->ctx->current_v2g_msg = V2G_PAYMENT_DETAILS_MSG; exi_out->V2G_Message.Body.PaymentDetailsRes_isUsed = 1u; init_iso2_PaymentDetailsResType(&exi_out->V2G_Message.Body.PaymentDetailsRes); next_v2g_event = handle_iso_payment_details(conn); // [V2G2-559] } else if (exi_in->V2G_Message.Body.AuthorizationReq_isUsed) { dlog(DLOG_LEVEL_TRACE, "Handling AuthorizationReq"); conn->ctx->current_v2g_msg = V2G_AUTHORIZATION_MSG; if (conn->ctx->last_v2g_msg != V2G_AUTHORIZATION_MSG) { if (conn->ctx->session.iso_selected_payment_option == iso2_paymentOptionType_ExternalPayment) { conn->ctx->p_charger->publish_require_auth_eim(nullptr); } } exi_out->V2G_Message.Body.AuthorizationRes_isUsed = 1u; init_iso2_AuthorizationResType(&exi_out->V2G_Message.Body.AuthorizationRes); next_v2g_event = handle_iso_authorization(conn); // [V2G2-562] } else if (exi_in->V2G_Message.Body.ChargeParameterDiscoveryReq_isUsed) { dlog(DLOG_LEVEL_TRACE, "Handling ChargeParameterDiscoveryReq"); conn->ctx->current_v2g_msg = V2G_CHARGE_PARAMETER_DISCOVERY_MSG; if (conn->ctx->last_v2g_msg == V2G_AUTHORIZATION_MSG) { dlog(DLOG_LEVEL_INFO, "Parameter-phase started"); } exi_out->V2G_Message.Body.ChargeParameterDiscoveryRes_isUsed = 1u; init_iso2_ChargeParameterDiscoveryResType(&exi_out->V2G_Message.Body.ChargeParameterDiscoveryRes); next_v2g_event = handle_iso_charge_parameter_discovery(conn); // [V2G2-565] } else if (exi_in->V2G_Message.Body.PowerDeliveryReq_isUsed) { dlog(DLOG_LEVEL_TRACE, "Handling PowerDeliveryReq"); conn->ctx->current_v2g_msg = V2G_POWER_DELIVERY_MSG; exi_out->V2G_Message.Body.PowerDeliveryRes_isUsed = 1u; init_iso2_PowerDeliveryResType(&exi_out->V2G_Message.Body.PowerDeliveryRes); next_v2g_event = handle_iso_power_delivery(conn); // [V2G2-589] } else if (exi_in->V2G_Message.Body.ChargingStatusReq_isUsed) { dlog(DLOG_LEVEL_TRACE, "Handling ChargingStatusReq"); conn->ctx->current_v2g_msg = V2G_CHARGING_STATUS_MSG; exi_out->V2G_Message.Body.ChargingStatusRes_isUsed = 1u; init_iso2_ChargingStatusResType(&exi_out->V2G_Message.Body.ChargingStatusRes); next_v2g_event = handle_iso_charging_status(conn); } else if (exi_in->V2G_Message.Body.MeteringReceiptReq_isUsed) { dlog(DLOG_LEVEL_TRACE, "Handling MeteringReceiptReq"); conn->ctx->current_v2g_msg = V2G_METERING_RECEIPT_MSG; exi_out->V2G_Message.Body.MeteringReceiptRes_isUsed = 1u; init_iso2_MeteringReceiptResType(&exi_out->V2G_Message.Body.MeteringReceiptRes); next_v2g_event = handle_iso_metering_receipt(conn); // [V2G2-796] } else if (exi_in->V2G_Message.Body.CertificateUpdateReq_isUsed) { dlog(DLOG_LEVEL_TRACE, "Handling CertificateUpdateReq"); conn->ctx->current_v2g_msg = V2G_CERTIFICATE_UPDATE_MSG; exi_out->V2G_Message.Body.CertificateUpdateRes_isUsed = 1u; init_iso2_CertificateUpdateResType(&exi_out->V2G_Message.Body.CertificateUpdateRes); next_v2g_event = handle_iso_certificate_update(conn); // [V2G2-556] } else if (exi_in->V2G_Message.Body.CertificateInstallationReq_isUsed) { dlog(DLOG_LEVEL_TRACE, "Handling CertificateInstallationReq"); conn->ctx->current_v2g_msg = V2G_CERTIFICATE_INSTALLATION_MSG; dlog(DLOG_LEVEL_INFO, "CertificateInstallation-phase started"); exi_out->V2G_Message.Body.CertificateInstallationRes_isUsed = 1u; init_iso2_CertificateInstallationResType(&exi_out->V2G_Message.Body.CertificateInstallationRes); next_v2g_event = handle_iso_certificate_installation(conn); // [V2G2-553] } else if (exi_in->V2G_Message.Body.CableCheckReq_isUsed) { dlog(DLOG_LEVEL_TRACE, "Handling CableCheckReq"); conn->ctx->current_v2g_msg = V2G_CABLE_CHECK_MSG; /* At first send mqtt charging phase signal to the customer interface */ if (V2G_CHARGE_PARAMETER_DISCOVERY_MSG == conn->ctx->last_v2g_msg) { conn->ctx->p_charger->publish_start_cable_check(nullptr); } exi_out->V2G_Message.Body.CableCheckRes_isUsed = 1u; init_iso2_CableCheckResType(&exi_out->V2G_Message.Body.CableCheckRes); next_v2g_event = handle_iso_cable_check(conn); // [V2G2-583 } else if (exi_in->V2G_Message.Body.PreChargeReq_isUsed) { dlog(DLOG_LEVEL_TRACE, "Handling PreChargeReq"); conn->ctx->current_v2g_msg = V2G_PRE_CHARGE_MSG; /* At first send mqtt charging phase signal to the customer interface */ if (conn->ctx->last_v2g_msg == V2G_CABLE_CHECK_MSG) { conn->ctx->p_charger->publish_start_pre_charge(nullptr); dlog(DLOG_LEVEL_INFO, "Precharge-phase started"); } exi_out->V2G_Message.Body.PreChargeRes_isUsed = 1u; init_iso2_PreChargeResType(&exi_out->V2G_Message.Body.PreChargeRes); next_v2g_event = handle_iso_pre_charge(conn); // [V2G2-586] } else if (exi_in->V2G_Message.Body.WeldingDetectionReq_isUsed) { dlog(DLOG_LEVEL_TRACE, "Handling WeldingDetectionReq"); conn->ctx->current_v2g_msg = V2G_WELDING_DETECTION_MSG; if (conn->ctx->last_v2g_msg != V2G_WELDING_DETECTION_MSG) { dlog(DLOG_LEVEL_INFO, "Welding-phase started"); } exi_out->V2G_Message.Body.WeldingDetectionRes_isUsed = 1u; init_iso2_WeldingDetectionResType(&exi_out->V2G_Message.Body.WeldingDetectionRes); next_v2g_event = handle_iso_welding_detection(conn); // [V2G2-596] } else if (exi_in->V2G_Message.Body.SessionStopReq_isUsed) { dlog(DLOG_LEVEL_TRACE, "Handling SessionStopReq"); conn->ctx->current_v2g_msg = V2G_SESSION_STOP_MSG; exi_out->V2G_Message.Body.SessionStopRes_isUsed = 1u; init_iso2_SessionStopResType(&exi_out->V2G_Message.Body.SessionStopRes); next_v2g_event = handle_iso_session_stop(conn); // [V2G2-570] } else { dlog(DLOG_LEVEL_ERROR, "create_response_message: request type not found"); next_v2g_event = V2G_EVENT_IGNORE_MSG; } dlog(DLOG_LEVEL_TRACE, "Current state: %s", conn->ctx->is_dc_charger ? iso_dc_states[conn->ctx->state].description : iso_ac_states[conn->ctx->state].description); // If next_v2g_event == V2G_EVENT_IGNORE_MSG, keep the current state and ignore msg if (next_v2g_event != V2G_EVENT_IGNORE_MSG) { conn->ctx->last_v2g_msg = conn->ctx->current_v2g_msg; /* Configure session id */ memcpy(exi_out->V2G_Message.Header.SessionID.bytes, &conn->ctx->evse_v2g_data.session_id, iso2_sessionIDType_BYTES_SIZE); /* We always set bytesLen to iso2_MessageHeaderType_SessionID_BYTES_SIZE */ exi_out->V2G_Message.Header.SessionID.bytesLen = iso2_sessionIDType_BYTES_SIZE; } return next_v2g_event; }