Files
Eric F d398a6ced2 Add extracted tools: CitrineOS, OpenOCPP, ShapeShifter
- 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
2026-06-08 00:38:27 -04:00

1134 lines
61 KiB
C++

// SPDX-License-Identifier: Apache-2.0
// Copyright (C) 2023 chargebyte GmbH
// Copyright (C) 2023 Contributors to EVerest
#include <cbv2g/din/din_msgDefDatatypes.h>
#include <inttypes.h>
#include <string.h>
#include "din_server.hpp"
#include "log.hpp"
#include "tools.hpp"
#include "v2g_ctx.hpp"
#include "v2g_server.hpp"
#define SASCHEDULETUPLEID 1
/**
* @brief load_din_physical_value This function copies the physical values from source to destination struct.
* @param phy_value_dest is the destination struct.
* @param phy_value_source is the source struct.
*/
void load_din_physical_value(struct din_PhysicalValueType* const phy_value_dest,
struct iso2_PhysicalValueType* const phy_value_source) {
phy_value_dest->Multiplier = phy_value_source->Multiplier;
phy_value_dest->Value = phy_value_source->Value;
phy_value_dest->Unit_isUsed = 1;
switch (phy_value_source->Unit) {
case iso2_unitSymbolType_h:
phy_value_dest->Unit = din_unitSymbolType_h;
break;
case iso2_unitSymbolType_m:
phy_value_dest->Unit = din_unitSymbolType_m;
break;
case iso2_unitSymbolType_s:
phy_value_dest->Unit = din_unitSymbolType_s;
break;
case iso2_unitSymbolType_A:
phy_value_dest->Unit = din_unitSymbolType_A;
break;
case iso2_unitSymbolType_V:
phy_value_dest->Unit = din_unitSymbolType_V;
break;
case iso2_unitSymbolType_W:
phy_value_dest->Unit = din_unitSymbolType_W;
break;
case iso2_unitSymbolType_Wh:
phy_value_dest->Unit = din_unitSymbolType_Wh;
break;
default:
phy_value_dest->Unit = din_unitSymbolType_h;
break;
}
}
/*!
* \brief din_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. \param state is the current state of the charging session \param current_v2g_msg is the current handled V2G
* message \param state of the actual session. \return Returns a din_ResponseCode with sequence error if current_v2g_msg
* is not expected, otherwise OK.
*/
static din_responseCodeType din_validate_state(int state, enum V2gMsgTypeId current_v2g_msg) {
/* if the request type has enabled (= set) bit in expected_requests, then this
* message is ok at this stage; otherwise reject it.
*/
return (din_states[state].allowed_requests & (1 << current_v2g_msg)) ? din_responseCodeType_OK
: din_responseCodeType_FAILED_SequenceError;
}
namespace utils {
/*!
* \brief din_validate_response_code This function checks if an external error has occurred (sequence error, user
* abort)... ). \param din_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 the next
* v2g-event.
*/
v2g_event din_validate_response_code(din_responseCodeType* const din_response_code, struct v2g_connection const* conn) {
enum v2g_event nextEvent = V2G_EVENT_NO_EVENT;
din_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)) {
*din_response_code = din_responseCodeType_FAILED;
}
/* [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 = din_validate_state(conn->ctx->state, conn->ctx->current_v2g_msg);
*din_response_code = (response_code_tmp >= din_responseCodeType_FAILED) ? response_code_tmp : *din_response_code;
/* [V2G-DC-391]: check whether the session id matches the expected one of the active session */
*din_response_code = ((conn->ctx->current_v2g_msg != V2G_SESSION_SETUP_MSG) &&
(conn->ctx->evse_v2g_data.session_id != conn->ctx->ev_v2g_data.received_session_id))
? din_responseCodeType_FAILED_UnknownSession
: *din_response_code;
if ((conn->ctx->terminate_connection_on_failed_response == true) &&
(*din_response_code >= din_responseCodeType_FAILED)) {
nextEvent = V2G_EVENT_SEND_AND_TERMINATE; // [V2G-DC-665]
}
/* log failed response code message */
if (*din_response_code >= din_responseCodeType_FAILED &&
*din_response_code <= din_responseCodeType_FAILED_WrongEnergyTransferType) {
dlog(DLOG_LEVEL_ERROR, "Failed response code detected for message \"%s\", error: %s",
v2g_msg_type[conn->ctx->current_v2g_msg], dinResponse[*din_response_code]);
}
return nextEvent;
}
} // namespace utils
/*!
* \brief publish_DIN_DcEvStatus This function is a helper function to publish EVStatusType.
* \param ctx is a pointer to the V2G context.
* \param din_ev_status the structure the holds the EV Status elements.
*/
static void publish_DIN_DcEvStatus(struct v2g_context* ctx, const struct din_DC_EVStatusType& din_ev_status) {
if ((ctx->ev_v2g_data.din_dc_ev_status.EVErrorCode != din_ev_status.EVErrorCode) ||
(ctx->ev_v2g_data.din_dc_ev_status.EVReady != din_ev_status.EVReady) ||
(ctx->ev_v2g_data.din_dc_ev_status.EVRESSSOC != din_ev_status.EVRESSSOC)) {
ctx->ev_v2g_data.din_dc_ev_status.EVErrorCode = din_ev_status.EVErrorCode;
ctx->ev_v2g_data.din_dc_ev_status.EVReady = din_ev_status.EVReady;
ctx->ev_v2g_data.din_dc_ev_status.EVRESSSOC = din_ev_status.EVRESSSOC;
types::iso15118::DcEvStatus ev_status;
ev_status.dc_ev_error_code = static_cast<types::iso15118::DcEvErrorCode>(din_ev_status.EVErrorCode);
ev_status.dc_ev_ready = din_ev_status.EVReady;
ev_status.dc_ev_ress_soc = static_cast<float>(din_ev_status.EVRESSSOC);
ctx->p_charger->publish_dc_ev_status(ev_status);
}
}
//=============================================
// Publishing request msg
//=============================================
/*!
* \brief publish_din_service_discovery_req This function publishes the din_ServiceDiscoveryReqType message to the MQTT
* interface. \param ctx is the V2G context. \param din_ServiceDiscoveryReqType is the request message.
*/
static void
publish_din_service_discovery_req(struct v2g_context* ctx,
struct din_ServiceDiscoveryReqType const* const v2g_service_discovery_req) {
// V2G values that can be published: ServiceCategory, ServiceScope
}
/*!
* \brief publish_din_service_payment_selection_req This function publishes the din_ServicePaymentSelectionReqType
* message to the MQTT interface.
* \param ctx is the V2G context.
* \param v2g_payment_service_selection_req is the request message.
*/
static void publish_din_service_payment_selection_req(
struct v2g_context* ctx, struct din_ServicePaymentSelectionReqType const* const v2g_payment_service_selection_req) {
// V2G values that can be published: selected_payment_option, SelectedServiceList
}
/*!
* \brief publish_din_charge_parameter_discovery_req This function publishes the din_ChargeParameterDiscoveryReqType
* message to the MQTT interface.
* \param ctx is the V2G context.
* \param v2g_charge_parameter_discovery_req is the request message.
*/
static void publish_din_charge_parameter_discovery_req(
struct v2g_context* ctx,
struct din_ChargeParameterDiscoveryReqType const* const v2g_charge_parameter_discovery_req) {
// V2G values that can be published: DC_EVChargeParameter, MaxEntriesSAScheduleTuple
types::iso15118::EnergyTransferMode transfer_mode{};
switch (v2g_charge_parameter_discovery_req->EVRequestedEnergyTransferType) {
case din_EVRequestedEnergyTransferType_AC_single_phase_core:
transfer_mode = types::iso15118::EnergyTransferMode::AC_single_phase_core;
break;
case din_EVRequestedEnergyTransferType_AC_three_phase_core:
transfer_mode = types::iso15118::EnergyTransferMode::AC_three_phase_core;
break;
case din_EVRequestedEnergyTransferType_DC_core:
transfer_mode = types::iso15118::EnergyTransferMode::DC_core;
break;
case din_EVRequestedEnergyTransferType_DC_extended:
transfer_mode = types::iso15118::EnergyTransferMode::DC_extended;
break;
case din_EVRequestedEnergyTransferType_DC_combo_core:
transfer_mode = types::iso15118::EnergyTransferMode::DC_combo_core;
break;
case din_EVRequestedEnergyTransferType_DC_unique:
transfer_mode = types::iso15118::EnergyTransferMode::DC_unique;
break;
default:
dlog(DLOG_LEVEL_WARNING, "Unable to convert RequestedEnergyTransferType to EnergyTransferMode: %d",
v2g_charge_parameter_discovery_req->EVRequestedEnergyTransferType);
}
ctx->p_charger->publish_requested_energy_transfer_mode(transfer_mode);
if (v2g_charge_parameter_discovery_req->DC_EVChargeParameter_isUsed == (unsigned int)1) {
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));
}
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));
}
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);
}
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);
}
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_DIN_DcEvStatus(ctx, v2g_charge_parameter_discovery_req->DC_EVChargeParameter.DC_EVStatus);
}
}
/*!
* \brief publish_din_power_delivery_req This function publishes the din_PowerDeliveryReqType message to the MQTT
* interface. \param ctx is the V2G context. \param din_power_delivery_req is the request message.
*/
static void publish_din_power_delivery_req(struct v2g_context* ctx,
struct din_PowerDeliveryReqType const* const v2g_power_delivery_req) {
// V2G values that can be published: ReadyToChargeState
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_DIN_DcEvStatus(ctx, v2g_power_delivery_req->DC_EVPowerDeliveryParameter.DC_EVStatus);
}
}
/*!
* \brief publish_din_precharge_req This function publishes the din_PreChargeReqType message to the MQTT interface.
* \param ctx is the V2G context.
* \param v2g_precharge_req is the request message.
*/
static void publish_din_precharge_req(struct v2g_context* ctx,
struct din_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_DIN_DcEvStatus(ctx, v2g_precharge_req->DC_EVStatus);
}
/*!
* \brief publish_din_current_demand_req This function publishes the din_CurrentDemandReqType message to the MQTT
* interface. \param ctx is the V2G context. \param v2g_current_demand_req is the request message.
*/
static void publish_din_current_demand_req(struct v2g_context* ctx,
struct din_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_DIN_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);
}
//=============================================
// Request Handling
//=============================================
namespace states {
/*!
* \brief handle_iso_session_setup This function handles the din_session_setup 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. [V2G-DC-436]
* \param conn holds the structure with the V2G msg pair.
* \return Returns the next V2G-event.
*/
enum v2g_event handle_din_session_setup(struct v2g_connection* conn) {
struct din_SessionSetupReqType* req = &conn->exi_in.dinEXIDocument->V2G_Message.Body.SessionSetupReq;
struct din_SessionSetupResType* res = &conn->exi_out.dinEXIDocument->V2G_Message.Body.SessionSetupRes;
enum v2g_event nextEvent = 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());
/* Now fill the evse response message */
res->ResponseCode = din_responseCodeType_OK_NewSessionEstablished; // [V2G-DC-393]
/* Check and init session id */
/* If the customer doesen't select a session id, generate one */
srand((unsigned int)time(NULL));
if (conn->ctx->evse_v2g_data.session_id == (uint64_t)0) {
generate_random_data(&conn->ctx->evse_v2g_data.session_id, 8);
dlog(DLOG_LEVEL_INFO, "No session_id found. Generating random session id.");
}
dlog(DLOG_LEVEL_INFO, "Created new session with id 0x%016" PRIx64, be64toh(conn->ctx->evse_v2g_data.session_id));
res->EVSEID.bytesLen = std::min((int)conn->ctx->evse_v2g_data.evse_id.bytesLen, iso2_EVSEID_CHARACTER_SIZE);
memcpy(res->EVSEID.bytes, conn->ctx->evse_v2g_data.evse_id.bytes, res->EVSEID.bytesLen);
res->DateTimeNow_isUsed = conn->ctx->evse_v2g_data.date_time_now_is_used;
res->DateTimeNow = time(NULL);
/* Check the current response code and check if no external error has occurred */
nextEvent = utils::din_validate_response_code(&res->ResponseCode, conn);
/* Set next expected req msg */
conn->ctx->state = WAIT_FOR_SERVICEDISCOVERY; // [V2G-DC-438]
return nextEvent;
}
/*!
* \brief handle_din_service_discovery This function handles the din 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. [V2G-DC-440]
* \param conn is the structure with the V2G msg pair.
* \return Returns the next V2G-event.
*/
enum v2g_event handle_din_service_discovery(struct v2g_connection* conn) {
struct din_ServiceDiscoveryReqType* req = &conn->exi_in.dinEXIDocument->V2G_Message.Body.ServiceDiscoveryReq;
struct din_ServiceDiscoveryResType* res = &conn->exi_out.dinEXIDocument->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_din_service_discovery_req(conn->ctx, req);
/* Now fill the evse response message */
res->ResponseCode = din_responseCodeType_OK; // [V2G-DC-388]
if ((conn->ctx->evse_v2g_data.charge_service.SupportedEnergyTransferMode.EnergyTransferMode.array[0] !=
iso2_EnergyTransferModeType_DC_core) &&
(conn->ctx->evse_v2g_data.charge_service.SupportedEnergyTransferMode.EnergyTransferMode.array[0]) !=
iso2_EnergyTransferModeType_DC_extended) {
conn->ctx->evse_v2g_data.charge_service.SupportedEnergyTransferMode.EnergyTransferMode.array[0] =
iso2_EnergyTransferModeType_DC_extended;
dlog(DLOG_LEVEL_WARNING, "Selected EnergyTransferType is not supported in DIN 70121. Correcting value of field "
"SupportedEnergyTransferType0 to 'DC_extended'");
}
res->ChargeService.ServiceTag.ServiceID =
conn->ctx->evse_v2g_data.charge_service.ServiceID; // ID of the charge service
res->ChargeService.ServiceTag.ServiceName_isUsed = (unsigned int)0; // [V2G-DC-628] Shall not be used in DIN 70121
res->ChargeService.ServiceTag.ServiceScope_isUsed = (unsigned int)0; // [V2G-DC-629] Shall not be used in DIN 70121
res->ChargeService.ServiceTag.ServiceCategory = din_serviceCategoryType_EVCharging; // Is fixed
res->ChargeService.FreeService = conn->ctx->evse_v2g_data.charge_service.FreeService;
res->ChargeService.EnergyTransferType =
(din_EVSESupportedEnergyTransferType)
conn->ctx->evse_v2g_data.charge_service.SupportedEnergyTransferMode.EnergyTransferMode.array[0];
res->ServiceList_isUsed = 0; // Shall not be used in DIN 70121
// In the scope of DIN 70121 only ExternalPayment shall be used.
res->PaymentOptions.PaymentOption.array[0] = din_paymentOptionType_ExternalPayment;
res->PaymentOptions.PaymentOption.arrayLen = 1;
/* Check the current response code and check if no external error has occurred */
nextEvent = utils::din_validate_response_code(&res->ResponseCode, conn);
/* Set next expected req msg */
conn->ctx->state = WAIT_FOR_PAYMENTSERVICESELECTION; // [V2G-DC-441]
return nextEvent;
}
} // namespace states
/*!
* \brief handle_din_service_discovery This function handles the din service payment 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. [V2G-DC-443]
* \param conn is the structure with the V2G msg pair.
* \return Returns the next V2G-event.
*/
static enum v2g_event handle_din_service_payment_selection(struct v2g_connection* conn) {
struct din_ServicePaymentSelectionReqType* req =
&conn->exi_in.dinEXIDocument->V2G_Message.Body.ServicePaymentSelectionReq;
struct din_ServicePaymentSelectionResType* res =
&conn->exi_out.dinEXIDocument->V2G_Message.Body.ServicePaymentSelectionRes;
enum v2g_event nextEvent = V2G_EVENT_NO_EVENT;
/* At first, publish the received EV request message to the customer MQTT interface */
publish_din_service_payment_selection_req(conn->ctx, req);
/* Now fill the evse response message */
res->ResponseCode = din_responseCodeType_OK; // [V2G-DC-388]
res->ResponseCode = (req->SelectedPaymentOption != din_paymentOptionType_ExternalPayment)
? din_responseCodeType_FAILED_PaymentSelectionInvalid
: res->ResponseCode; // [V2G-DC-395]
res->ResponseCode = (req->SelectedServiceList.SelectedService.arrayLen == (uint16_t)1 &&
(req->SelectedServiceList.SelectedService.array[0].ServiceID ==
conn->ctx->evse_v2g_data.charge_service.ServiceID))
? res->ResponseCode
: din_responseCodeType_FAILED_ServiceSelectionInvalid; // [V2G-DC-396] [V2G-DC-635] (List
// shall be limited to 1)
/* Check the current response code and check if no external error has occurred */
nextEvent = utils::din_validate_response_code(&res->ResponseCode, conn);
/* Set next expected req msg */
conn->ctx->state = WAIT_FOR_AUTHORIZATION; // [V2G-DC-444]
return nextEvent;
}
/*!
* \brief handle_din_service_discovery This function handles the din contract authentication 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. [V2G-DC-494]
* \param conn is the structure with the V2G msg pair.
* \return Returns the next V2G-event.
*/
enum v2g_event states::handle_din_contract_authentication(struct v2g_connection* conn) {
struct din_ContractAuthenticationResType* res =
&conn->exi_out.dinEXIDocument->V2G_Message.Body.ContractAuthenticationRes;
enum v2g_event nextEvent = V2G_EVENT_NO_EVENT;
/* Fill the EVSE response message */
if (conn->ctx->session.authorization_rejected == true) {
res->ResponseCode = din_responseCodeType_FAILED;
} else {
res->ResponseCode = din_responseCodeType_OK; // [V2G-DC-388]
}
res->EVSEProcessing = (conn->ctx->evse_v2g_data.evse_processing[PHASE_AUTH] == (uint8_t)0)
? din_EVSEProcessingType_Finished
: din_EVSEProcessingType_Ongoing;
/* Check the current response code and check if no external error has occurred */
nextEvent = utils::din_validate_response_code(&res->ResponseCode, conn);
/* Set next expected req msg */
conn->ctx->state = (res->EVSEProcessing == din_EVSEProcessingType_Ongoing)
? WAIT_FOR_AUTHORIZATION
: WAIT_FOR_CHARGEPARAMETERDISCOVERY; // [V2G-DC-444], [V2G-DC-497]
return nextEvent;
}
/*!
* \brief handle_din_charge_parameter This function handles the din charge parameters 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. [V2G-DC-445]
* \param conn is the structure with the v2g msg pair.
* \return Returns the next V2G-event.
*/
static enum v2g_event handle_din_charge_parameter(struct v2g_connection* conn) {
struct din_ChargeParameterDiscoveryReqType* req =
&conn->exi_in.dinEXIDocument->V2G_Message.Body.ChargeParameterDiscoveryReq;
struct din_ChargeParameterDiscoveryResType* res =
&conn->exi_out.dinEXIDocument->V2G_Message.Body.ChargeParameterDiscoveryRes;
enum v2g_event nextEvent = V2G_EVENT_NO_EVENT;
/* At first, publish the received EV request message to the customer MQTT interface */
publish_din_charge_parameter_discovery_req(conn->ctx, req);
/* Now fill the EVSE response message */
res->ResponseCode = din_responseCodeType_OK; // [V2G-DC-388]
res->AC_EVSEChargeParameter_isUsed = 0u;
if (((req->EVRequestedEnergyTransferType != din_EVRequestedEnergyTransferType_DC_core) &&
(req->EVRequestedEnergyTransferType != din_EVRequestedEnergyTransferType_DC_extended)) ||
conn->ctx->evse_v2g_data.charge_service.SupportedEnergyTransferMode.EnergyTransferMode.array[0] !=
(iso2_EnergyTransferModeType)req->EVRequestedEnergyTransferType) {
res->ResponseCode = din_responseCodeType_FAILED_WrongEnergyTransferType; // [V2G-DC-397] Failed reponse code is
// logged at the end of the function
} else {
log_selected_energy_transfer_type((int)req->EVRequestedEnergyTransferType);
}
res->ResponseCode = (req->AC_EVChargeParameter_isUsed == (unsigned int)1)
? din_responseCodeType_FAILED_WrongChargeParameter
: res->ResponseCode; // [V2G-DC-398]
/* DC_EVSEChargeParameter */
res->DC_EVSEChargeParameter.DC_EVSEStatus.EVSEIsolationStatus =
(din_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 =
(din_EVSENotificationType)conn->ctx->evse_v2g_data.evse_notification;
res->DC_EVSEChargeParameter.DC_EVSEStatus.EVSEStatusCode =
(true == conn->ctx->intl_emergency_shutdown)
? din_DC_EVSEStatusCodeType_EVSE_EmergencyShutdown
: (din_DC_EVSEStatusCodeType)conn->ctx->evse_v2g_data.evse_status_code[PHASE_PARAMETER];
res->DC_EVSEChargeParameter.DC_EVSEStatus.NotificationMaxDelay = conn->ctx->evse_v2g_data.notification_max_delay;
if (conn->ctx->evse_v2g_data.evse_current_regulation_tolerance_is_used) {
load_din_physical_value(&res->DC_EVSEChargeParameter.EVSECurrentRegulationTolerance,
&conn->ctx->evse_v2g_data.evse_current_regulation_tolerance);
res->DC_EVSEChargeParameter.EVSECurrentRegulationTolerance.Unit_isUsed = (unsigned int)1;
}
res->DC_EVSEChargeParameter.EVSECurrentRegulationTolerance_isUsed =
conn->ctx->evse_v2g_data.evse_current_regulation_tolerance_is_used;
load_din_physical_value(&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;
load_din_physical_value(&res->DC_EVSEChargeParameter.EVSEMaximumCurrentLimit,
&conn->ctx->evse_v2g_data.power_capabilities.max_current);
load_din_physical_value(&res->DC_EVSEChargeParameter.EVSEMaximumPowerLimit,
&conn->ctx->evse_v2g_data.power_capabilities.max_power);
res->DC_EVSEChargeParameter.EVSEMaximumPowerLimit_isUsed =
conn->ctx->evse_v2g_data.evse_maximum_power_limit_is_used;
load_din_physical_value(&res->DC_EVSEChargeParameter.EVSEMaximumVoltageLimit,
&conn->ctx->evse_v2g_data.power_capabilities.max_voltage);
load_din_physical_value(&res->DC_EVSEChargeParameter.EVSEMinimumCurrentLimit,
&conn->ctx->evse_v2g_data.power_capabilities.min_current);
load_din_physical_value(&res->DC_EVSEChargeParameter.EVSEMinimumVoltageLimit,
&conn->ctx->evse_v2g_data.power_capabilities.min_voltage);
load_din_physical_value(&res->DC_EVSEChargeParameter.EVSEPeakCurrentRipple,
&conn->ctx->evse_v2g_data.evse_peak_current_ripple);
res->DC_EVSEChargeParameter_isUsed = (unsigned int)1;
// res->EVSEChargeParameter.noContent
res->EVSEChargeParameter_isUsed = (unsigned int)0;
res->EVSEProcessing = ((uint8_t)0 == conn->ctx->evse_v2g_data.evse_processing[PHASE_PARAMETER])
? din_EVSEProcessingType_Finished
: din_EVSEProcessingType_Ongoing;
if ((unsigned int)1 == res->DC_EVSEChargeParameter.EVSEMaximumPowerLimit_isUsed) {
res->SAScheduleList.SAScheduleTuple.array[0].PMaxSchedule.PMaxScheduleEntry.array[0].PMax =
(SHRT_MAX < (conn->ctx->evse_v2g_data.power_capabilities.max_power.Value *
pow(10, conn->ctx->evse_v2g_data.power_capabilities.max_power.Multiplier)))
? SHRT_MAX
: (conn->ctx->evse_v2g_data.power_capabilities.max_power.Value *
pow(10, conn->ctx->evse_v2g_data.power_capabilities.max_power.Multiplier));
} else {
res->SAScheduleList.SAScheduleTuple.array[0].PMaxSchedule.PMaxScheduleEntry.array[0].PMax = SHRT_MAX;
}
constexpr auto PAUSE_DURATION = 60 * 30;
res->SAScheduleList.SAScheduleTuple.array[0].PMaxSchedule.PMaxScheduleEntry.array[0].RelativeTimeInterval.duration =
conn->ctx->evse_v2g_data.no_energy_pause == NoEnergyPauseStatus::None ? SA_SCHEDULE_DURATION
: PAUSE_DURATION; // Must cover 24 hours
res->SAScheduleList.SAScheduleTuple.array[0]
.PMaxSchedule.PMaxScheduleEntry.array[0]
.RelativeTimeInterval.duration_isUsed = 1; // Must be used in DIN
res->SAScheduleList.SAScheduleTuple.array[0].PMaxSchedule.PMaxScheduleEntry.array[0].RelativeTimeInterval.start = 0;
res->SAScheduleList.SAScheduleTuple.array[0].PMaxSchedule.PMaxScheduleEntry.array[0].RelativeTimeInterval_isUsed =
1;
res->SAScheduleList.SAScheduleTuple.array[0].PMaxSchedule.PMaxScheduleEntry.array[0].TimeInterval_isUsed =
0; // no content
res->SAScheduleList.SAScheduleTuple.array[0].SalesTariff_isUsed =
0; // In the scope of DIN 70121, this optional element shall not be used. [V2G-DC-554]
res->SAScheduleList.SAScheduleTuple.array[0].SAScheduleTupleID = SASCHEDULETUPLEID;
res->SAScheduleList.SAScheduleTuple.array[0].PMaxSchedule.PMaxScheduleEntry.arrayLen = 1;
res->SAScheduleList.SAScheduleTuple.array[0].PMaxSchedule.PMaxScheduleID = 1;
res->SAScheduleList.SAScheduleTuple.arrayLen = 1;
res->SAScheduleList_isUsed = (unsigned int)1;
// res->SASchedules.noContent
res->SASchedules_isUsed = (unsigned int)0;
constexpr auto physical_value_to_float = [](const din_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(res->DC_EVSEChargeParameter.EVSEMinimumCurrentLimit);
const auto evse_minimum_voltage_limit =
physical_value_to_float(res->DC_EVSEChargeParameter.EVSEMinimumVoltageLimit);
if (ev_maximum_current_limit <= evse_minimum_current_limit ||
ev_maximum_voltage_limit <= evse_minimum_voltage_limit) {
res->ResponseCode = din_responseCodeType_FAILED_WrongChargeParameter;
res->DC_EVSEChargeParameter.DC_EVSEStatus.EVSEStatusCode = din_DC_EVSEStatusCodeType_EVSE_Shutdown;
}
if (res->EVSEProcessing == din_EVSEProcessingType_Finished and
conn->ctx->evse_v2g_data.no_energy_pause != NoEnergyPauseStatus::None) {
res->DC_EVSEChargeParameter.DC_EVSEStatus.EVSENotification = din_EVSENotificationType_StopCharging;
res->DC_EVSEChargeParameter.DC_EVSEStatus.NotificationMaxDelay = 0;
}
/* Check the current response code and check if no external error has occurred */
nextEvent = utils::din_validate_response_code(&res->ResponseCode, conn);
/* Set next expected req msg */
if (res->EVSEProcessing == din_EVSEProcessingType_Finished) {
if (res->DC_EVSEChargeParameter.DC_EVSEStatus.EVSEStatusCode != din_DC_EVSEStatusCodeType_EVSE_Ready) {
dlog(DLOG_LEVEL_WARNING,
"EVSE wants to finish charge parameter phase, but status code is not set to 'ready' (1)");
}
if (conn->ctx->evse_v2g_data.no_energy_pause == NoEnergyPauseStatus::BeforeCableCheck or
conn->ctx->evse_v2g_data.no_energy_pause == NoEnergyPauseStatus::AfterCableCheckPreCharge) {
conn->ctx->state = WAIT_FOR_SESSIONSTOP; // IEC61851-23:2023 CC.5.3.2
} else {
conn->ctx->state = WAIT_FOR_CABLECHECK; // [V2G-DC-453]
}
} else {
conn->ctx->state = WAIT_FOR_CHARGEPARAMETERDISCOVERY; // [V2G-DC-498]
}
return nextEvent;
}
/*!
* \brief handle_din_service_discovery This function handles the din 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. [V2G-DC-461]
* \param conn is the structure with the V2G msg pair.
* \return Returns the next V2G-event.
*/
static enum v2g_event handle_din_power_delivery(struct v2g_connection* conn) {
struct din_PowerDeliveryReqType* req = &conn->exi_in.dinEXIDocument->V2G_Message.Body.PowerDeliveryReq;
struct din_PowerDeliveryResType* res = &conn->exi_out.dinEXIDocument->V2G_Message.Body.PowerDeliveryRes;
enum v2g_event nextEvent = V2G_EVENT_NO_EVENT;
/* At first, publish the received EV request message to the MQTT interface */
publish_din_power_delivery_req(conn->ctx, req);
if (req->ReadyToChargeState == (int)0) {
conn->ctx->p_charger->publish_current_demand_finished(nullptr);
conn->ctx->p_charger->publish_dc_open_contactor(nullptr);
conn->ctx->session.is_charging = false;
} else {
conn->ctx->p_charger->publish_v2g_setup_finished(nullptr);
}
/* Now fill the evse response message */
res->ResponseCode = din_responseCodeType_OK; // [V2G-DC-388]
res->AC_EVSEStatus_isUsed = (unsigned int)0;
res->DC_EVSEStatus.EVSEIsolationStatus = (din_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 = (din_EVSENotificationType)conn->ctx->evse_v2g_data.evse_notification;
res->DC_EVSEStatus.NotificationMaxDelay = conn->ctx->evse_v2g_data.notification_max_delay;
res->DC_EVSEStatus.EVSEStatusCode =
(conn->ctx->intl_emergency_shutdown == true)
? din_DC_EVSEStatusCodeType_EVSE_EmergencyShutdown
: (din_DC_EVSEStatusCodeType)conn->ctx->evse_v2g_data.evse_status_code[PHASE_CHARGE];
res->DC_EVSEStatus_isUsed = (unsigned int)1;
res->EVSEStatus_isUsed = (unsigned int)0;
// res->EVSEStatus.noContent
/* Check response code */
if (req->ChargingProfile_isUsed == (unsigned int)1) {
/* Check the selected SAScheduleTupleID */
res->ResponseCode = (req->ChargingProfile.SAScheduleTupleID != (int16_t)SASCHEDULETUPLEID)
? din_responseCodeType_FAILED_TariffSelectionInvalid
: res->ResponseCode; // [V2G-DC-400]
for (uint16_t idx = 0; idx < req->ChargingProfile.ProfileEntry.arrayLen; idx++) {
bool entry_found = (req->ChargingProfile.ProfileEntry.array[idx].ChargingProfileEntryStart <=
(uint32_t)SA_SCHEDULE_DURATION);
if ((entry_found == false) || (req->ChargingProfile.ProfileEntry.array[idx].ChargingProfileEntryMaxPower >
(conn->ctx->evse_v2g_data.evse_maximum_power_limit.Value *
pow(10, conn->ctx->evse_v2g_data.evse_maximum_power_limit.Multiplier)))) {
// res->ResponseCode = din_responseCodeType_FAILED_ChargingProfileInvalid; //[V2G-DC-399]. Currently
// commented to increase compatibility with EV'S
dlog(DLOG_LEVEL_WARNING, "EV's charging profile is invalid (ChargingProfileEntryMaxPower %d too high)!",
req->ChargingProfile.ProfileEntry.array[idx].ChargingProfileEntryMaxPower);
break;
}
}
}
res->ResponseCode = ((req->ReadyToChargeState == (int)1) &&
(res->DC_EVSEStatus.EVSEStatusCode != din_DC_EVSEStatusCodeType_EVSE_Ready))
? din_responseCodeType_FAILED_PowerDeliveryNotApplied
: res->ResponseCode; // [V2G-DC-401]
/* Check the current response code and check if no external error has occurred */
nextEvent = utils::din_validate_response_code(&res->ResponseCode, conn);
/* Set next expected req msg */
if ((req->ReadyToChargeState == (int)1) && (V2G_CURRENT_DEMAND_MSG != conn->ctx->last_v2g_msg)) {
conn->ctx->state = WAIT_FOR_CURRENTDEMAND; // [V2G-DC-462]
} else {
/* abort charging session if EV is ready to charge after current demand phase */
if (req->ReadyToChargeState == (int)1) {
res->ResponseCode = din_responseCodeType_FAILED;
nextEvent = V2G_EVENT_SEND_AND_TERMINATE;
}
conn->ctx->state = WAIT_FOR_WELDINGDETECTION_SESSIONSTOP; // [V2G-DC-459]
}
return nextEvent;
}
/*!
* \brief handle_din_service_discovery This function handles the din 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. [V2G-DC-454]
* \param conn is the structure with the V2G msg pair.
* \return Returns the next V2G-event.
*/
static enum v2g_event handle_din_cable_check(struct v2g_connection* conn) {
struct din_CableCheckReqType* req = &conn->exi_in.dinEXIDocument->V2G_Message.Body.CableCheckReq;
struct din_CableCheckResType* res = &conn->exi_out.dinEXIDocument->V2G_Message.Body.CableCheckRes;
enum v2g_event nextEvent = V2G_EVENT_NO_EVENT;
/* At first, publish the received EV request message to the MQTT interface */
publish_DIN_DcEvStatus(conn->ctx, req->DC_EVStatus);
// TODO: Wait for CP state C [V2G-DC-547]
/* Now fill the evse response message */
res->ResponseCode = din_responseCodeType_OK; // [V2G-DC-388]
res->DC_EVSEStatus.EVSEIsolationStatus = (din_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<din_EVSENotificationType>(conn->ctx->evse_v2g_data.evse_notification);
res->DC_EVSEStatus.NotificationMaxDelay = static_cast<uint32_t>(conn->ctx->evse_v2g_data.notification_max_delay);
res->EVSEProcessing = (conn->ctx->evse_v2g_data.evse_processing[PHASE_ISOLATION] == (uint8_t)0)
? din_EVSEProcessingType_Finished
: din_EVSEProcessingType_Ongoing;
if (conn->ctx->intl_emergency_shutdown == true) {
res->DC_EVSEStatus.EVSEStatusCode = din_DC_EVSEStatusCodeType_EVSE_EmergencyShutdown;
} else if (res->EVSEProcessing == din_EVSEProcessingType_Finished) {
res->DC_EVSEStatus.EVSEStatusCode = din_DC_EVSEStatusCodeType_EVSE_Ready;
} else {
res->DC_EVSEStatus.EVSEStatusCode =
static_cast<din_DC_EVSEStatusCodeType>(conn->ctx->evse_v2g_data.evse_status_code[PHASE_ISOLATION]);
}
/* Check the current response code and check if no external error has occurred */
nextEvent = utils::din_validate_response_code(&res->ResponseCode, conn);
/* Set next expected req msg */
if ((res->EVSEProcessing == din_EVSEProcessingType_Finished) &&
(res->DC_EVSEStatus.EVSEIsolationStatus_isUsed == (unsigned int)1) &&
((res->DC_EVSEStatus.EVSEIsolationStatus == din_isolationLevelType_Valid) ||
(res->DC_EVSEStatus.EVSEIsolationStatus == din_isolationLevelType_Warning)) &&
(res->DC_EVSEStatus.EVSEStatusCode == din_DC_EVSEStatusCodeType_EVSE_Ready)) {
conn->ctx->state = WAIT_FOR_PRECHARGE; // [V2G-DC-499]
} else {
if ((res->EVSEProcessing == din_EVSEProcessingType_Finished) &&
((res->DC_EVSEStatus.EVSEIsolationStatus_isUsed == (unsigned int)0) ||
((res->DC_EVSEStatus.EVSEIsolationStatus != din_isolationLevelType_Valid) &&
(din_isolationLevelType_Warning != res->DC_EVSEStatus.EVSEIsolationStatus)))) {
dlog(DLOG_LEVEL_WARNING, "EVSE wants to finish cable check phase, but either status code is not set to "
"'ready' (1) or isolation status is not valid");
}
conn->ctx->state = WAIT_FOR_CABLECHECK; // [V2G-DC-455]
}
return nextEvent;
}
/*!
* \brief handle_din_service_discovery This function handles the din 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. [V2G-DC-457]
* \param conn is the structure with the V2G msg pair.
* \return Returns the next V2G-event.
*/
static enum v2g_event handle_din_pre_charge(struct v2g_connection* conn) {
struct din_PreChargeReqType* req = &conn->exi_in.dinEXIDocument->V2G_Message.Body.PreChargeReq;
struct din_PreChargeResType* res = &conn->exi_out.dinEXIDocument->V2G_Message.Body.PreChargeRes;
enum v2g_event nextEvent = V2G_EVENT_NO_EVENT;
/* At first, publish the received EV request message to the customer MQTT interface */
publish_din_precharge_req(conn->ctx, req);
/* Now fill the EVSE response message */
res->ResponseCode = din_responseCodeType_OK; // [V2G-DC-388]
res->DC_EVSEStatus.EVSEIsolationStatus = (din_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 = (din_EVSENotificationType)conn->ctx->evse_v2g_data.evse_notification;
res->DC_EVSEStatus.EVSEStatusCode =
(conn->ctx->intl_emergency_shutdown == true)
? din_DC_EVSEStatusCodeType_EVSE_EmergencyShutdown
: (din_DC_EVSEStatusCodeType)conn->ctx->evse_v2g_data.evse_status_code[PHASE_PRECHARGE];
res->DC_EVSEStatus.NotificationMaxDelay = conn->ctx->evse_v2g_data.notification_max_delay;
load_din_physical_value(&res->EVSEPresentVoltage, &conn->ctx->evse_v2g_data.evse_present_voltage);
/* Check the current response code and check if no external error has occurred */
nextEvent = utils::din_validate_response_code(&res->ResponseCode, conn);
/* Set next expected req msg */
conn->ctx->state = WAIT_FOR_PRECHARGE_POWERDELIVERY; // [V2G-DC-458]
return nextEvent;
}
/*!
* \brief handle_din_service_discovery This function handles the din 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. [V2G-DC-464]
* \param conn is the structure with the V2G msg pair.
* \return Returns the next V2G-event.
*/
static enum v2g_event handle_din_current_demand(struct v2g_connection* conn) {
struct din_CurrentDemandReqType* req = &conn->exi_in.dinEXIDocument->V2G_Message.Body.CurrentDemandReq;
struct din_CurrentDemandResType* res = &conn->exi_out.dinEXIDocument->V2G_Message.Body.CurrentDemandRes;
enum v2g_event nextEvent = V2G_EVENT_NO_EVENT;
/* At first, publish the received EV request message to the MQTT interface */
publish_din_current_demand_req(conn->ctx, req);
/* Now fill the evse response message */
res->ResponseCode = din_responseCodeType_OK; // [V2G-DC-388]
res->DC_EVSEStatus.EVSEIsolationStatus = (din_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 = (din_EVSENotificationType)conn->ctx->evse_v2g_data.evse_notification;
res->DC_EVSEStatus.EVSEStatusCode =
(conn->ctx->intl_emergency_shutdown == true)
? din_DC_EVSEStatusCodeType_EVSE_EmergencyShutdown
: (din_DC_EVSEStatusCodeType)conn->ctx->evse_v2g_data.evse_status_code[PHASE_CHARGE];
res->DC_EVSEStatus.NotificationMaxDelay = (uint32_t)conn->ctx->evse_v2g_data.notification_max_delay;
res->EVSECurrentLimitAchieved = conn->ctx->evse_v2g_data.evse_current_limit_achieved;
res->EVSEMaximumCurrentLimit_isUsed = conn->ctx->evse_v2g_data.evse_maximum_current_limit_is_used;
load_din_physical_value(&res->EVSEMaximumCurrentLimit, &conn->ctx->evse_v2g_data.evse_maximum_current_limit);
res->EVSEMaximumPowerLimit_isUsed = conn->ctx->evse_v2g_data.evse_maximum_power_limit_is_used;
load_din_physical_value(&res->EVSEMaximumPowerLimit, &conn->ctx->evse_v2g_data.evse_maximum_power_limit);
res->EVSEMaximumVoltageLimit_isUsed = conn->ctx->evse_v2g_data.evse_maximum_voltage_limit_is_used;
load_din_physical_value(&res->EVSEMaximumVoltageLimit, &conn->ctx->evse_v2g_data.evse_maximum_voltage_limit);
res->EVSEPowerLimitAchieved = conn->ctx->evse_v2g_data.evse_power_limit_achieved;
load_din_physical_value(&res->EVSEPresentCurrent, &conn->ctx->evse_v2g_data.evse_present_current);
load_din_physical_value(&res->EVSEPresentVoltage, &conn->ctx->evse_v2g_data.evse_present_voltage);
res->EVSEVoltageLimitAchieved = conn->ctx->evse_v2g_data.evse_voltage_limit_achieved;
/* Check the current response code and check if no external error has occurred */
nextEvent = utils::din_validate_response_code(&res->ResponseCode, conn);
/* Set next expected req msg */
conn->ctx->state = WAIT_FOR_CURRENTDEMAND_POWERDELIVERY; // [V2G-DC-465]
return nextEvent;
}
/*!
* \brief handle_din_service_discovery This function handles the din 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. [V2G-DC-468]
* \param conn is the structure with the V2G msg pair.
* \return Returns the next V2G-event.
*/
static enum v2g_event handle_din_welding_detection(struct v2g_connection* conn) {
struct din_WeldingDetectionReqType* req = &conn->exi_in.dinEXIDocument->V2G_Message.Body.WeldingDetectionReq;
struct din_WeldingDetectionResType* res = &conn->exi_out.dinEXIDocument->V2G_Message.Body.WeldingDetectionRes;
enum v2g_event nextEvent = V2G_EVENT_NO_EVENT;
/* At first, publish the received EV request message to the MQTT interface */
publish_DIN_DcEvStatus(conn->ctx, req->DC_EVStatus);
/* TODO: Waiting for CP-State B [V2G-DC-556]. */
/* Now fill the evse response message */
res->ResponseCode = din_responseCodeType_OK; // [V2G-DC-388]
res->DC_EVSEStatus.EVSEIsolationStatus = (din_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 = (din_EVSENotificationType)conn->ctx->evse_v2g_data.evse_notification;
res->DC_EVSEStatus.EVSEStatusCode =
(conn->ctx->intl_emergency_shutdown == true)
? din_DC_EVSEStatusCodeType_EVSE_EmergencyShutdown
: (din_DC_EVSEStatusCodeType)conn->ctx->evse_v2g_data.evse_status_code[PHASE_WELDING];
res->DC_EVSEStatus.NotificationMaxDelay = (uint32_t)conn->ctx->evse_v2g_data.notification_max_delay;
load_din_physical_value(&res->EVSEPresentVoltage, &conn->ctx->evse_v2g_data.evse_present_voltage);
/* Check the current response code and check if no external error has occurred */
nextEvent = utils::din_validate_response_code(&res->ResponseCode, conn);
/* Set next expected req msg */
conn->ctx->state = WAIT_FOR_WELDINGDETECTION_SESSIONSTOP; // [V2G-DC-469]
return nextEvent;
}
/*!
* \brief handle_din_session_stop This function handles the din session stop 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. [V2G-DC-450]
* \param conn is the structure with the V2G msg pair.
* \return Returns the next V2G-event.
*/
static enum v2g_event handle_din_session_stop(struct v2g_connection* conn) {
struct din_SessionStopResType* res = &conn->exi_out.dinEXIDocument->V2G_Message.Body.SessionStopRes;
/* Now fill the EVSE response message */
res->ResponseCode = din_responseCodeType_OK; // [V2G-DC-388]
/* Check the current response code and check if no external error has occurred */
utils::din_validate_response_code(&res->ResponseCode, conn);
/* Setuo dlink action */
conn->d_link_action = dLinkAction::D_LINK_ACTION_TERMINATE;
/* Set next expected req msg */
conn->ctx->state = WAIT_FOR_TERMINATED_SESSION; // [V2G-DC-451]
/* Check the current response code and check if no external error has occurred */
return V2G_EVENT_SEND_AND_TERMINATE; // Charging must be terminated after sending the response message.
}
enum v2g_event din_handle_request(v2g_connection* conn) {
using namespace states;
struct din_exiDocument* exi_in = conn->exi_in.dinEXIDocument;
struct din_exiDocument* exi_out = conn->exi_out.dinEXIDocument;
enum v2g_event next_v2g_event = V2G_EVENT_TERMINATE_CONNECTION; // ERROR_UNEXPECTED_REQUEST_MESSAGE;
/* extract session id */
conn->ctx->ev_v2g_data.received_session_id = v2g_session_id_from_exi(false, exi_in);
/* init V2G structure (document, header, body) */
init_din_exiDocument(exi_out);
init_din_MessageHeaderType(&exi_out->V2G_Message.Header);
exi_out->V2G_Message.Header.SessionID.bytesLen = 8;
init_din_BodyType(&exi_out->V2G_Message.Body);
// === Start request handling ===
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_din_CurrentDemandResType(&exi_out->V2G_Message.Body.CurrentDemandRes);
next_v2g_event = handle_din_current_demand(conn);
} 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_din_SessionSetupResType(&exi_out->V2G_Message.Body.SessionSetupRes);
/* Handle v2g msg */
next_v2g_event = handle_din_session_setup(conn);
} 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_din_ServiceDiscoveryResType(&exi_out->V2G_Message.Body.ServiceDiscoveryRes);
next_v2g_event = handle_din_service_discovery(conn);
} else if (exi_in->V2G_Message.Body.ServicePaymentSelectionReq_isUsed) {
dlog(DLOG_LEVEL_TRACE, "Handling PaymentServiceSelectionReq");
conn->ctx->current_v2g_msg = V2G_PAYMENT_SERVICE_SELECTION_MSG;
exi_out->V2G_Message.Body.ServicePaymentSelectionRes_isUsed = 1u;
init_din_ServicePaymentSelectionResType(&exi_out->V2G_Message.Body.ServicePaymentSelectionRes);
next_v2g_event = handle_din_service_payment_selection(conn);
} else if (exi_in->V2G_Message.Body.ContractAuthenticationReq_isUsed) {
dlog(DLOG_LEVEL_TRACE, "Handling ContractAuthenticationReq");
conn->ctx->current_v2g_msg = V2G_AUTHORIZATION_MSG;
if (conn->ctx->last_v2g_msg != V2G_AUTHORIZATION_MSG) {
dlog(DLOG_LEVEL_INFO, "Auth-phase started");
conn->ctx->p_charger->publish_require_auth_eim(nullptr);
}
exi_out->V2G_Message.Body.ContractAuthenticationRes_isUsed = 1u;
init_din_ContractAuthenticationResType(&exi_out->V2G_Message.Body.ContractAuthenticationRes);
next_v2g_event = handle_din_contract_authentication(conn);
} else if (exi_in->V2G_Message.Body.PaymentDetailsReq_isUsed) {
dlog(DLOG_LEVEL_ERROR, "PaymentDetails request is not supported in DIN 70121");
conn->ctx->current_v2g_msg = V2G_UNKNOWN_MSG;
next_v2g_event = V2G_EVENT_IGNORE_MSG;
} else if (exi_in->V2G_Message.Body.ServiceDetailReq_isUsed) {
dlog(DLOG_LEVEL_ERROR, "ServiceDetail request is not supported in DIN 70121");
conn->ctx->current_v2g_msg = V2G_UNKNOWN_MSG;
next_v2g_event = V2G_EVENT_IGNORE_MSG;
} 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_din_ChargeParameterDiscoveryResType(&exi_out->V2G_Message.Body.ChargeParameterDiscoveryRes);
next_v2g_event = handle_din_charge_parameter(conn);
} else if (exi_in->V2G_Message.Body.CableCheckReq_isUsed) {
dlog(DLOG_LEVEL_TRACE, "Handling CableCheckReq");
conn->ctx->current_v2g_msg = V2G_CABLE_CHECK_MSG;
if (conn->ctx->last_v2g_msg == V2G_CHARGE_PARAMETER_DISCOVERY_MSG) {
conn->ctx->p_charger->publish_start_cable_check(nullptr);
dlog(DLOG_LEVEL_INFO, "Isolation-phase started");
}
exi_out->V2G_Message.Body.CableCheckRes_isUsed = 1u;
init_din_CableCheckResType(&exi_out->V2G_Message.Body.CableCheckRes);
next_v2g_event = handle_din_cable_check(conn);
} else if (exi_in->V2G_Message.Body.PreChargeReq_isUsed) {
dlog(DLOG_LEVEL_TRACE, "Handling PreChargeReq");
conn->ctx->current_v2g_msg = V2G_PRE_CHARGE_MSG;
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_din_PreChargeResType(&exi_out->V2G_Message.Body.PreChargeRes);
next_v2g_event = handle_din_pre_charge(conn);
} else if (exi_in->V2G_Message.Body.PowerDeliveryReq_isUsed) {
dlog(DLOG_LEVEL_TRACE, "Handling PowerDeliveryReq");
conn->ctx->current_v2g_msg = V2G_POWER_DELIVERY_MSG;
if (conn->ctx->last_v2g_msg == V2G_PRE_CHARGE_MSG) {
dlog(DLOG_LEVEL_INFO, "Charge-phase started");
}
exi_out->V2G_Message.Body.PowerDeliveryRes_isUsed = 1u;
init_din_PowerDeliveryResType(&exi_out->V2G_Message.Body.PowerDeliveryRes);
next_v2g_event = handle_din_power_delivery(conn);
} else if (exi_in->V2G_Message.Body.ChargingStatusReq_isUsed) {
dlog(DLOG_LEVEL_TRACE, "ChargingStatus request is not supported in DIN 70121");
conn->ctx->current_v2g_msg = V2G_UNKNOWN_MSG;
exi_out->V2G_Message.Body.ChargingStatusRes_isUsed = 0u;
init_din_ChargingStatusResType(&exi_out->V2G_Message.Body.ChargingStatusRes);
next_v2g_event = V2G_EVENT_IGNORE_MSG;
} else if (exi_in->V2G_Message.Body.MeteringReceiptReq_isUsed) {
dlog(DLOG_LEVEL_TRACE, "MeteringReceipt request is not supported in DIN 70121");
conn->ctx->current_v2g_msg = V2G_UNKNOWN_MSG;
exi_out->V2G_Message.Body.MeteringReceiptRes_isUsed = 0u;
init_din_MeteringReceiptResType(&exi_out->V2G_Message.Body.MeteringReceiptRes);
next_v2g_event = V2G_EVENT_IGNORE_MSG;
} else if (exi_in->V2G_Message.Body.CertificateUpdateReq_isUsed) {
dlog(DLOG_LEVEL_TRACE, "CertificateUpdate request is not supported in DIN 70121");
conn->ctx->current_v2g_msg = V2G_UNKNOWN_MSG;
next_v2g_event = V2G_EVENT_IGNORE_MSG;
} else if (exi_in->V2G_Message.Body.CertificateInstallationReq_isUsed) {
dlog(DLOG_LEVEL_TRACE, "CertificateInstallation request is not supported in DIN 70121");
conn->ctx->current_v2g_msg = V2G_UNKNOWN_MSG;
next_v2g_event = V2G_EVENT_IGNORE_MSG;
} 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_POWER_DELIVERY_MSG) {
dlog(DLOG_LEVEL_INFO, "Welding-phase started");
}
exi_out->V2G_Message.Body.WeldingDetectionRes_isUsed = 1u;
init_din_WeldingDetectionResType(&exi_out->V2G_Message.Body.WeldingDetectionRes);
next_v2g_event = handle_din_welding_detection(conn);
} 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_din_SessionStopResType(&exi_out->V2G_Message.Body.SessionStopRes);
next_v2g_event = handle_din_session_stop(conn);
} else {
dlog(DLOG_LEVEL_ERROR, "Create_response_message: request type not found");
next_v2g_event = V2G_EVENT_IGNORE_MSG;
}
if (next_v2g_event != V2G_EVENT_IGNORE_MSG) {
/* Configure session id */
memcpy(exi_out->V2G_Message.Header.SessionID.bytes, &conn->ctx->evse_v2g_data.session_id, sizeof(uint64_t));
// TODO: Set byteLen
exi_out->V2G_Message.Header.SessionID.bytesLen = din_sessionIDType_BYTES_SIZE;
dlog(DLOG_LEVEL_TRACE, "Current state: %s", din_states[conn->ctx->state].description);
conn->ctx->last_v2g_msg = conn->ctx->current_v2g_msg;
}
return next_v2g_event;
}