// SPDX-License-Identifier: Apache-2.0 // Copyright (C) 2023 chargebyte GmbH // Copyright (C) 2023 Contributors to EVerest #include #include #include #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(din_ev_status.EVErrorCode); ev_status.dc_ev_ready = din_ev_status.EVReady; ev_status.dc_ev_ress_soc = static_cast(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(conn->ctx->evse_v2g_data.evse_notification); res->DC_EVSEStatus.NotificationMaxDelay = static_cast(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(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; }