- CitrineOS core extracted (CSMS OCPP 2.0.1) - OpenOCPP extracted (firmware OCPP 1.6J/2.0.1) - ShapeShifter library installed (pip install -e) - ShapeShifter specification extracted - EVerest extracted TODO updated with progress
2401 lines
129 KiB
C++
2401 lines
129 KiB
C++
// SPDX-License-Identifier: Apache-2.0
|
|
// Copyright (C) 2023 chargebyte GmbH
|
|
// Copyright (C) 2023 Contributors to EVerest
|
|
#include <cbv2g/common/exi_bitstream.h>
|
|
#include <cbv2g/exi_v2gtp.h> //for V2GTP_HEADER_LENGTHs
|
|
#include <cbv2g/iso_2/iso2_msgDefDatatypes.h>
|
|
#include <cbv2g/iso_2/iso2_msgDefDecoder.h>
|
|
#include <cbv2g/iso_2/iso2_msgDefEncoder.h>
|
|
#include <everest/tls/openssl_util.hpp>
|
|
|
|
#include <algorithm>
|
|
#include <cstdint>
|
|
#include <inttypes.h>
|
|
#include <math.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#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<types::iso15118::DcEvErrorCode>(iso2_ev_status.EVErrorCode);
|
|
ev_status.dc_ev_ready = iso2_ev_status.EVReady;
|
|
ev_status.dc_ev_ress_soc = static_cast<float>(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<iso2_DC_EVSEStatusCodeType>(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<size_t>(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<int16_t>(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<int>(parameter.value.bool_value.value());
|
|
out_parameter.boolValue_isUsed = true;
|
|
} else if (parameter.value.byte_value.has_value()) {
|
|
out_parameter.byteValue = static_cast<int8_t>(parameter.value.byte_value.value());
|
|
out_parameter.byteValue_isUsed = true;
|
|
} else if (parameter.value.short_value.has_value()) {
|
|
out_parameter.shortValue = static_cast<int16_t>(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<size_t, std::vector<types::iso15118_vas::SelectedService>> 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<std::vector<types::iso15118::CertificateHashDataInfo>> 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<iso2_EVSENotificationType>(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<iso2_EVSENotificationType>(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<iso2_EVSEProcessingType>(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<iso2_EVSENotificationType>(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<iso2_EVSENotificationType>(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<iso2_EVSENotificationType>(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<int>(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<int>(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<int>(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<int>(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;
|
|
}
|