Implemented a working mechanism for pausing and resuming a charging session

This commit is contained in:
Marc Mültin 2019-10-01 11:30:09 +02:00
parent 7d7cbf9cad
commit e6f7b4fd4e
29 changed files with 382 additions and 220 deletions

View File

@ -55,9 +55,9 @@ contract.certificate.update.timespan = 14
# SessionID
#----------
#
# If this value is unequal to zero, then it represents a previously
# paused V2G communication session
session.id = 0
# Hexadecimal string representing a byte array. If this value is unequal to "00", then it represents a
# previously paused V2G communication session
session.id = 00
# Selected payment option
@ -141,4 +141,4 @@ exi.codec = exificient
# Used for the PreCharge target voltage. The present voltage indicated by the charging station in PreChargeRes can deviate from the present voltage
# set in PreChargeReq by an EV-specific deviation factor. This value is given in percent.
# Example: voltage.accuracy = 10 means: present voltage may deviate from target voltage by 10 percent in order to successfully stop PreCharge
voltage.accuracy = 5
voltage.accuracy = 5

View File

@ -32,8 +32,10 @@ import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.v2gclarity.risev2g.evcc.session.V2GCommunicationSessionEVCC;
import com.v2gclarity.risev2g.shared.enumerations.CPStates;
import com.v2gclarity.risev2g.shared.utils.MiscUtils;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ACEVChargeParameterType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargingProfileType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargingSessionType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.DCEVChargeParameterType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.DCEVErrorCodeType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.DCEVPowerDeliveryParameterType;
@ -63,21 +65,31 @@ public class DummyEVController implements IACEVController, IDCEVController {
}
@Override
public PaymentOptionType getPaymentOption(PaymentOptionListType paymentOptionsOffered) {
// Contract payment option may only be chosen if offered by SECC AND if communication is secured by TLS
if (paymentOptionsOffered.getPaymentOption().contains(PaymentOptionType.CONTRACT)) {
if (!getCommSessionContext().isTlsConnection()) {
getLogger().warn("SECC offered CONTRACT based payment although no TLS connectionis used. Choosing EIM instead");
return PaymentOptionType.EXTERNAL_PAYMENT;
} else return PaymentOptionType.CONTRACT;
} else return PaymentOptionType.EXTERNAL_PAYMENT;
public PaymentOptionType getPaymentOption() {
/*
* The payment options offered by the SECC should probably be displayed on a HMI in the EV.
* A request to the EVController should then be initiated here in order to let the user
* choose which offered payment option to use.
*
* TODO check [V2G2-828] (selecting payment option related to state B, C)
*/
// Set default to Plug & Charge
return PaymentOptionType.CONTRACT;
}
@Override
public EnergyTransferModeType getRequestedEnergyTransferMode() {
return EnergyTransferModeType.AC_SINGLE_PHASE_CORE;
// Set default to AC_THREE_PHASE_CORE. Should normally depend on type of cable plugged into the vehicle inlet
EnergyTransferModeType requestedEnergyTransferMode = (EnergyTransferModeType) MiscUtils.getPropertyValue("energy.transfermode.requested");
if (requestedEnergyTransferMode == null)
return EnergyTransferModeType.AC_THREE_PHASE_CORE;
else
return requestedEnergyTransferMode;
}
@Override
public JAXBElement<ACEVChargeParameterType> getACEVChargeParamter() {
@ -320,6 +332,13 @@ public class DummyEVController implements IACEVController, IDCEVController {
return true;
} else
/*
* OPTIONAL:
* If you want to trigger a pause of the charging session, then uncomment this line
*/
//getCommSessionContext().setChargingSession(ChargingSessionType.PAUSE);
return false;
}

View File

@ -44,7 +44,7 @@ public interface IEVController {
* RFID card or via Plug-and-Charge (PnC)
* @return The payment option Contract or ExternalPayment
*/
public PaymentOptionType getPaymentOption(PaymentOptionListType paymentOptionsOffered);
public PaymentOptionType getPaymentOption();
/**

View File

@ -30,7 +30,7 @@ import com.v2gclarity.risev2g.shared.utils.MiscUtils;
public class StartEVCC {
public static void main(String[] args) {
MiscUtils.setV2gEntityConfig(GlobalValues.EVCC_CONFIG_PROPERTIES_PATH.toString());
MiscUtils.loadProperties(GlobalValues.EVCC_CONFIG_PROPERTIES_PATH.toString());
new V2GCommunicationSessionHandlerEVCC();
}

View File

@ -61,11 +61,14 @@ import com.v2gclarity.risev2g.shared.messageHandling.SendMessage;
import com.v2gclarity.risev2g.shared.messageHandling.TerminateSession;
import com.v2gclarity.risev2g.shared.misc.V2GCommunicationSession;
import com.v2gclarity.risev2g.shared.misc.V2GTPMessage;
import com.v2gclarity.risev2g.shared.utils.ByteUtils;
import com.v2gclarity.risev2g.shared.utils.MiscUtils;
import com.v2gclarity.risev2g.shared.utils.SecurityUtils.ContractCertificateStatus;
import com.v2gclarity.risev2g.shared.v2gMessages.appProtocol.AppProtocolType;
import com.v2gclarity.risev2g.shared.v2gMessages.appProtocol.SupportedAppProtocolRes;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargeParameterDiscoveryReqType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargingProfileType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargingSessionType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.EnergyTransferModeType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PaymentOptionType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.SAScheduleListType;
@ -87,9 +90,8 @@ public class V2GCommunicationSessionEVCC extends V2GCommunicationSession impleme
* (saves some processing time)
*/
private ChargeParameterDiscoveryReqType chargeParameterDiscoveryReq;
private boolean stopChargingRequested;
private ChargingSessionType chargingSession;
private boolean renegotiationRequested;
private boolean pausingV2GCommSession;
private ChargingProfileType chargingProfile;
private ServiceListType offeredServices;
private SelectedServiceListType selectedServices;
@ -148,6 +150,9 @@ public class V2GCommunicationSessionEVCC extends V2GCommunicationSession impleme
// Set default value for contract certificate status to UNKNOWN
setContractCertStatus(ContractCertificateStatus.UNKNOWN);
// ChargingSessionType only takes enum values "Pause" and "Terminate". Therefore, set it to null at beginning of charging session
setChargingSession(null);
getLogger().debug("\n*******************************************" +
"\n* New V2G communication session initialized" +
@ -231,12 +236,21 @@ public class V2GCommunicationSessionEVCC extends V2GCommunicationSession impleme
private void saveSessionProperties() {
// TODO save respective parameters to properties file
// According to [V2G2-740]
MiscUtils.getProperties().setProperty("session.id", "" + ByteUtils.toHexString(getSessionID()));
MiscUtils.getProperties().setProperty("authentication.mode", getSelectedPaymentOption().value());
MiscUtils.getProperties().setProperty("energy.transfermode.requested", getRequestedEnergyTransferMode().value());
MiscUtils.storeProperties(GlobalValues.EVCC_CONFIG_PROPERTIES_PATH.toString());
}
private void deleteSessionProperties() {
// TODO delete the respective parameters from properties file
// Reset the session ID and the authentication mode
MiscUtils.getProperties().setProperty("session.id", "00");
MiscUtils.getProperties().setProperty("authentication.mode", "");
MiscUtils.storeProperties(GlobalValues.EVCC_CONFIG_PROPERTIES_PATH.toString());
}
@ -283,13 +297,6 @@ public class V2GCommunicationSessionEVCC extends V2GCommunicationSession impleme
this.reactionToIncomingMessage = reactionToIncomingMessage;
}
public boolean isStopChargingRequested() {
return stopChargingRequested;
}
public void setStopChargingRequested(boolean stopChargingRequested) {
this.stopChargingRequested = stopChargingRequested;
}
public boolean isRenegotiationRequested() {
return renegotiationRequested;
@ -299,13 +306,6 @@ public class V2GCommunicationSessionEVCC extends V2GCommunicationSession impleme
this.renegotiationRequested = renegotiationRequested;
}
public boolean isPausingV2GCommSession() {
return pausingV2GCommSession;
}
public void setPausingV2GCommSession(boolean pausingV2GCommSession) {
this.pausingV2GCommSession = pausingV2GCommSession;
}
public long getEvseScheduleReceived() {
return evseScheduleReceived;
@ -508,4 +508,15 @@ public class V2GCommunicationSessionEVCC extends V2GCommunicationSession impleme
public void setSentGenChallenge(byte[] sentGenChallenge) {
this.sentGenChallenge = sentGenChallenge;
}
public ChargingSessionType getChargingSession() {
return chargingSession;
}
public void setChargingSession(ChargingSessionType chargingSession) {
this.chargingSession = chargingSession;
}
}

View File

@ -454,28 +454,19 @@ public abstract class ClientState extends State {
protected EnergyTransferModeType getRequestedEnergyTransferMode() {
// Check if an EnergyTransferModeType has been requested in a previously paused session
EnergyTransferModeType requestedEnergyTransferMode =
(EnergyTransferModeType) MiscUtils.getPropertyValue("energy.transfermode.requested");
EnergyTransferModeType requestedEnergyTransferMode = null;
if (requestedEnergyTransferMode == null) {
// Check if an EnergyTransferModeType has been requested in a previously paused session
if (getCommSessionContext().isOldSessionJoined())
requestedEnergyTransferMode = (EnergyTransferModeType) MiscUtils.getPropertyValue("energy.transfermode.requested");
if (requestedEnergyTransferMode == null)
requestedEnergyTransferMode = getCommSessionContext().getEvController().getRequestedEnergyTransferMode();
getCommSessionContext().setRequestedEnergyTransferMode(requestedEnergyTransferMode);
}
// We need to save the requested energy transfer mode in the session variable to be able to store in the properties file during pausing
getCommSessionContext().setRequestedEnergyTransferMode(requestedEnergyTransferMode);
return requestedEnergyTransferMode;
}
protected PaymentOptionType getSelectedPaymentOption() {
// Check if a PaymentOptionType has been requested in a previously paused session
PaymentOptionType selectedPaymentOption = (PaymentOptionType) MiscUtils.getPropertyValue("authentication.mode");
if (selectedPaymentOption == null) {
selectedPaymentOption = getCommSessionContext().getEvController().getPaymentOption(getCommSessionContext().getPaymentOptions());
getCommSessionContext().setSelectedPaymentOption(selectedPaymentOption);
}
return selectedPaymentOption;
}
}

View File

@ -39,6 +39,7 @@ import com.v2gclarity.risev2g.shared.utils.SecurityUtils;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ACEVSEChargeParameterType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargeParameterDiscoveryResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargeProgressType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargingSessionType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.DCEVSEChargeParameterType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.EVSENotificationType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.EVSEProcessingType;
@ -105,7 +106,7 @@ public class WaitForChargeParameterDiscoveryRes extends ClientState {
if (evseNotification.equals(EVSENotificationType.STOP_CHARGING)) {
getLogger().debug("The EVSE requested to stop the charging process");
getCommSessionContext().setStopChargingRequested(true);
getCommSessionContext().setChargingSession(ChargingSessionType.TERMINATE);
return getSendMessage(getPowerDeliveryReq(ChargeProgressType.STOP), V2GMessages.POWER_DELIVERY_RES);
} else {
@ -134,7 +135,23 @@ public class WaitForChargeParameterDiscoveryRes extends ClientState {
// Save the list of SASchedules (saves the time of reception as well)
getCommSessionContext().setSaSchedules(saSchedules);
if (getCommSessionContext().getEvController().getCPState().equals(CPStates.STATE_B)) {
/*
* The following states are possible (and will not raise the termination of a charging session):
* - State B:
* - In AC charging, when exchanging the first ChargeParameterDiscoveryReq/Res message pair, before the charging loop
* was initiated
* - State C:
* - In DC charging, when exchanging the first ChargeParameterDiscoveryReq/Res message pair, before the charging loop
* was initiated
* - In AC charging, after the charging loop was initiated and a renegotiation was triggered
*/
if (getCommSessionContext().getEvController().getCPState().equals(CPStates.STATE_B) ||
(getCommSessionContext().getEvController().getCPState().equals(CPStates.STATE_C) &&
getCommSessionContext().isRenegotiationRequested())) {
// We need to reset the renegotiation trigger (in case of State C and a renegotiation was triggered)
getCommSessionContext().setRenegotiationRequested(false);
if (getCommSessionContext().getRequestedEnergyTransferMode().toString().startsWith("AC")) {
return getSendMessage(getPowerDeliveryReq(ChargeProgressType.START), V2GMessages.POWER_DELIVERY_RES);
} else if (getCommSessionContext().getRequestedEnergyTransferMode().toString().startsWith("DC")) {

View File

@ -31,6 +31,7 @@ import com.v2gclarity.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import com.v2gclarity.risev2g.shared.messageHandling.TerminateSession;
import com.v2gclarity.risev2g.shared.utils.SecurityUtils;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargeProgressType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargingSessionType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargingStatusReqType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargingStatusResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.MeteringReceiptReqType;
@ -94,7 +95,8 @@ public class WaitForChargingStatusRes extends ClientState {
switch (chargingStatusRes.getACEVSEStatus().getEVSENotification()) {
case STOP_CHARGING:
getCommSessionContext().setStopChargingRequested(true);
getCommSessionContext().setChargingSession(ChargingSessionType.TERMINATE);
return getSendMessage(getPowerDeliveryReq(ChargeProgressType.STOP),
V2GMessages.POWER_DELIVERY_RES,
" (ChargeProgress = STOP_CHARGING)");
@ -117,7 +119,13 @@ public class WaitForChargingStatusRes extends ClientState {
return getSendMessage(chargingStatusReq, V2GMessages.CHARGING_STATUS_RES);
}
} else {
getCommSessionContext().setStopChargingRequested(true);
/* Check if the EV controller triggered a pause of a charging session.
* If not, indicate a termination of the charging session. This will be
* evaluated in the state WaitForPowerDeliveryRes
*/
if (getCommSessionContext().getChargingSession() == null)
getCommSessionContext().setChargingSession(ChargingSessionType.TERMINATE);
return getSendMessage(getPowerDeliveryReq(ChargeProgressType.STOP),
V2GMessages.POWER_DELIVERY_RES,
" (ChargeProgress = STOP_CHARGING)");

View File

@ -30,6 +30,7 @@ import com.v2gclarity.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import com.v2gclarity.risev2g.shared.messageHandling.TerminateSession;
import com.v2gclarity.risev2g.shared.utils.SecurityUtils;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargeProgressType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargingSessionType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargingStatusReqType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.CurrentDemandResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.DCEVSEStatusType;
@ -88,7 +89,8 @@ public class WaitForCurrentDemandRes extends ClientState {
switch ((EVSENotificationType) dcEVSEStatus.getEVSENotification()) {
case STOP_CHARGING:
getCommSessionContext().setStopChargingRequested(true);
getCommSessionContext().setChargingSession(ChargingSessionType.TERMINATE);
return getSendMessage(getPowerDeliveryReq(ChargeProgressType.STOP),
V2GMessages.POWER_DELIVERY_RES,
" (ChargeProgress = STOP_CHARGING)");
@ -110,7 +112,13 @@ public class WaitForCurrentDemandRes extends ClientState {
return getSendMessage(getCurrentDemandReq(), V2GMessages.CURRENT_DEMAND_RES);
}
} else {
getCommSessionContext().setStopChargingRequested(true);
/* Check if the EV controller triggered a pause of a charging session.
* If not, indicate a termination of the charging session. This will be
* evaluated in the state WaitForPowerDeliveryRes
*/
if (getCommSessionContext().getChargingSession() == null)
getCommSessionContext().setChargingSession(ChargingSessionType.TERMINATE);
return getSendMessage(getPowerDeliveryReq(ChargeProgressType.STOP),
V2GMessages.POWER_DELIVERY_RES,
" (ChargeProgress = STOP_CHARGING)");

View File

@ -29,6 +29,7 @@ import com.v2gclarity.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import com.v2gclarity.risev2g.shared.messageHandling.TerminateSession;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ACEVSEStatusType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargeProgressType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargingSessionType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargingStatusReqType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.DCEVSEStatusType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.EVSENotificationType;
@ -65,7 +66,8 @@ public class WaitForMeteringReceiptRes extends ClientState {
switch (evseNotification) {
case STOP_CHARGING:
getCommSessionContext().setStopChargingRequested(true);
getCommSessionContext().setChargingSession(ChargingSessionType.TERMINATE);
return getSendMessage(getPowerDeliveryReq(ChargeProgressType.STOP),
V2GMessages.POWER_DELIVERY_RES,
" (ChargeProgress = STOP_CHARGING)");
@ -97,7 +99,6 @@ public class WaitForMeteringReceiptRes extends ClientState {
return getSendMessage(getCurrentDemandReq(), V2GMessages.CURRENT_DEMAND_RES);
}
} else {
getCommSessionContext().setStopChargingRequested(true);
return getSendMessage(getPowerDeliveryReq(ChargeProgressType.STOP),
V2GMessages.POWER_DELIVERY_RES,
" (ChargeProgress = STOP_CHARGING)");

View File

@ -66,12 +66,13 @@ public class WaitForPowerDeliveryRes extends ClientState {
getCommSessionContext().setChangeToState(CPStates.STATE_B);
}
getCommSessionContext().setRenegotiationRequested(false);
return getSendMessage(getChargeParameterDiscoveryReq(), V2GMessages.CHARGE_PARAMETER_DISCOVERY_RES);
} else if (getCommSessionContext().isStopChargingRequested()) {
return getSendMessage(ChargingSessionType.TERMINATE, true);
} else if (getCommSessionContext().isPausingV2GCommSession()) {
return getSendMessage(ChargingSessionType.PAUSE, false);
} else if (getCommSessionContext().getChargingSession() != null &&
getCommSessionContext().getChargingSession() == ChargingSessionType.TERMINATE) {
return getSendMessage(ChargingSessionType.TERMINATE);
} else if (getCommSessionContext().getChargingSession() != null &&
getCommSessionContext().getChargingSession() == ChargingSessionType.PAUSE) {
return getSendMessage(ChargingSessionType.PAUSE);
} else {
if (getCommSessionContext().getRequestedEnergyTransferMode().toString().startsWith("AC")) {
ChargingStatusReqType chargingStatusReq = new ChargingStatusReqType();
@ -86,7 +87,7 @@ public class WaitForPowerDeliveryRes extends ClientState {
}
private ReactionToIncomingMessage getSendMessage(ChargingSessionType chargingSessionType, boolean stopChargingRequested) {
private ReactionToIncomingMessage getSendMessage(ChargingSessionType chargingSessionType) {
if (getCommSessionContext().getRequestedEnergyTransferMode().toString().startsWith("DC")) {
// CP state B signaling BEFORE sending WeldingDetectionReq message in DC
if (getCommSessionContext().getEvController().setCPState(CPStates.STATE_B)) {
@ -100,10 +101,7 @@ public class WaitForPowerDeliveryRes extends ClientState {
getCommSessionContext().getEvController().getCPState() +
")");
}
} else {
if (stopChargingRequested) getCommSessionContext().setStopChargingRequested(false);
else getCommSessionContext().setPausingV2GCommSession(false);
} else {
return getSendMessage(getSessionStopReq(chargingSessionType),
V2GMessages.SESSION_STOP_RES, "(ChargingSession = " +
chargingSessionType.toString() + ")");

View File

@ -28,9 +28,11 @@ import com.v2gclarity.risev2g.evcc.transportLayer.TLSClient;
import com.v2gclarity.risev2g.shared.enumerations.V2GMessages;
import com.v2gclarity.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import com.v2gclarity.risev2g.shared.messageHandling.TerminateSession;
import com.v2gclarity.risev2g.shared.utils.MiscUtils;
import com.v2gclarity.risev2g.shared.utils.SecurityUtils;
import com.v2gclarity.risev2g.shared.utils.SecurityUtils.ContractCertificateStatus;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.EnergyTransferModeType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PaymentOptionListType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PaymentOptionType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.SelectedServiceType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ServiceCategoryType;
@ -60,7 +62,7 @@ public class WaitForServiceDiscoveryRes extends ClientState {
*/
getCommSessionContext().getServiceDetailsToBeRequested().clear(); // just to be sure
// Save offered charge service and optional value added services
// Save the list containing information on all other services than charging services offered by the charging station
getCommSessionContext().setOfferedServices(serviceDiscoveryRes.getServiceList());
if (serviceDiscoveryRes.getChargeService() != null) {
@ -69,7 +71,6 @@ public class WaitForServiceDiscoveryRes extends ClientState {
if (serviceDiscoveryRes.getChargeService().getSupportedEnergyTransferMode()
.getEnergyTransferMode().contains(requestedEnergyTransferMode)) {
getCommSessionContext().setRequestedEnergyTransferMode(requestedEnergyTransferMode);
getCommSessionContext().getOfferedServices().getService().add(serviceDiscoveryRes.getChargeService());
addSelectedService(1, null); // Assumption: a charge service is always used
} else {
@ -77,16 +78,7 @@ public class WaitForServiceDiscoveryRes extends ClientState {
}
} else return new TerminateSession("No charge service available");
/*
* The payment options offered by the SECC should probably be displayed on a HMI in the EV.
* A request to the EVController should then be initiated here in order to let the user
* choose which offered payment option to use.
*
* TODO check [V2G2-828] (selecting payment option related to state B, C)
*/
PaymentOptionType userPaymentOption =
getCommSessionContext().getEvController().getPaymentOption(serviceDiscoveryRes.getPaymentOptionList());
getCommSessionContext().setSelectedPaymentOption(userPaymentOption);
getCommSessionContext().setSelectedPaymentOption(getSelectedPaymentOption(serviceDiscoveryRes.getPaymentOptionList()));
// Check for the usage of value added services (VAS)
if (useVAS(serviceDiscoveryRes)) {
@ -100,6 +92,41 @@ public class WaitForServiceDiscoveryRes extends ClientState {
}
protected PaymentOptionType getSelectedPaymentOption(PaymentOptionListType authenticationOptions) {
/*
* Note that although the type is called "PaymentOptionListType", it's not a list of payment options, but authorization options,
* namely either Plug & Charge ("Contract") or external identification means (EIM) like an RFID card ("ExternalPayment"). This is
* why the parameter for this function is called "authenticationOptions" for clarity.
*/
PaymentOptionType selectedPaymentOption = null;
// Check if a PaymentOptionType has been requested in a previously paused session
if (getCommSessionContext().isOldSessionJoined())
selectedPaymentOption = (PaymentOptionType) MiscUtils.getPropertyValue("authentication.mode");
if (selectedPaymentOption == null)
selectedPaymentOption = getCommSessionContext().getEvController().getPaymentOption();
// Contract payment option may only be chosen if offered by SECC AND if communication is secured by TLS
if (selectedPaymentOption.equals(PaymentOptionType.CONTRACT) &&
authenticationOptions.getPaymentOption().contains(PaymentOptionType.CONTRACT)) {
if (!getCommSessionContext().isTlsConnection()) {
getLogger().warn("SECC offered 'Contract' based payment although no TLS connectionis used. Choosing 'ExternalPayment' instead");
getCommSessionContext().setSelectedPaymentOption(PaymentOptionType.EXTERNAL_PAYMENT);
return PaymentOptionType.EXTERNAL_PAYMENT;
} else {
getCommSessionContext().setSelectedPaymentOption(PaymentOptionType.CONTRACT);
return PaymentOptionType.CONTRACT;
}
} else {
getCommSessionContext().setSelectedPaymentOption(PaymentOptionType.EXTERNAL_PAYMENT);
return PaymentOptionType.EXTERNAL_PAYMENT;
}
}
/**
* According to [V2G2-422] a ServiceDetailsReq is needed in case VAS (value added services)
* such as certificate installation/update are to be used and offered by the SECC.

View File

@ -47,14 +47,10 @@ public class WaitForSessionSetupRes extends ClientState {
(SessionSetupResType) ((V2GMessage) message).getBody().getBodyElement().getValue();
if (sessionSetupRes.getResponseCode().equals(ResponseCodeType.OK_NEW_SESSION_ESTABLISHED)) {
getCommSessionContext().setSessionID(receivedSessionID);
getLogger().debug("Negotiated session ID is " + ByteUtils.toLongFromByteArray(receivedSessionID));
getLogger().debug("Negotiated session ID is " + ByteUtils.toHexString(receivedSessionID));
getCommSessionContext().setOldSessionJoined(false);
getCommSessionContext().setEvseID(sessionSetupRes.getEVSEID());
// EVSETimeStamp is optional
if (sessionSetupRes.getEVSETimeStamp() != null) getCommSessionContext().setEvseTimeStamp(sessionSetupRes.getEVSETimeStamp());
} else if (sessionSetupRes.getResponseCode().equals(ResponseCodeType.OK_OLD_SESSION_JOINED)) {
getLogger().debug("Previous charging session joined (session ID = " + ByteUtils.toLongFromByteArray(receivedSessionID) + ")");
getLogger().debug("Previous charging session joined (session ID = " + ByteUtils.toHexString(receivedSessionID) + ")");
/*
* Mark that the old session was joined in order to resend
@ -63,8 +59,6 @@ public class WaitForSessionSetupRes extends ClientState {
* according to 8.4.2. Those values should be persisted in the properties file.
*/
getCommSessionContext().setOldSessionJoined(true);
getCommSessionContext().setEvseID(sessionSetupRes.getEVSEID());
getCommSessionContext().setEvseTimeStamp(sessionSetupRes.getEVSETimeStamp());
} else {
getCommSessionContext().setOldSessionJoined(false);
getLogger().error("No negative response code received, but positive response code '" +
@ -73,6 +67,12 @@ public class WaitForSessionSetupRes extends ClientState {
return new TerminateSession("Positive response code invalid in state WaitForSessionSetupRes");
}
getCommSessionContext().setSessionID(receivedSessionID);
getCommSessionContext().setEvseID(sessionSetupRes.getEVSEID());
// EVSETimeStamp is optional
if (sessionSetupRes.getEVSETimeStamp() != null)
getCommSessionContext().setEvseTimeStamp(sessionSetupRes.getEVSETimeStamp());
ServiceDiscoveryReqType serviceDiscoveryReq = new ServiceDiscoveryReqType();
/*

View File

@ -24,8 +24,10 @@
package com.v2gclarity.risev2g.evcc.states;
import com.v2gclarity.risev2g.evcc.session.V2GCommunicationSessionEVCC;
import com.v2gclarity.risev2g.shared.messageHandling.PauseSession;
import com.v2gclarity.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import com.v2gclarity.risev2g.shared.messageHandling.TerminateSession;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargingSessionType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.SessionStopResType;
public class WaitForSessionStopRes extends ClientState {
@ -37,7 +39,11 @@ public class WaitForSessionStopRes extends ClientState {
@Override
public ReactionToIncomingMessage processIncomingMessage(Object message) {
if (isIncomingMessageValid(message, SessionStopResType.class)) {
return new TerminateSession("V2G communication session will be stopped successfully", true);
if (getCommSessionContext().getChargingSession() != null &&
getCommSessionContext().getChargingSession().equals(ChargingSessionType.PAUSE))
return new PauseSession();
else
return new TerminateSession("V2G communication session will be stopped successfully", true);
} else {
return new TerminateSession("Incoming message raised an error");
}

View File

@ -30,6 +30,7 @@ import com.v2gclarity.risev2g.shared.enumerations.V2GMessages;
import com.v2gclarity.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import com.v2gclarity.risev2g.shared.messageHandling.TerminateSession;
import com.v2gclarity.risev2g.shared.misc.TimeRestrictions;
import com.v2gclarity.risev2g.shared.utils.ByteUtils;
import com.v2gclarity.risev2g.shared.utils.MiscUtils;
import com.v2gclarity.risev2g.shared.v2gMessages.appProtocol.AppProtocolType;
import com.v2gclarity.risev2g.shared.v2gMessages.appProtocol.ResponseCodeType;
@ -77,14 +78,18 @@ public class WaitForSupportedAppProtocolRes extends ClientState {
/*
* The session ID is taken from the properties file. If a previous charging session has been
* paused, then the previously valid session ID has been written to the properties file
* in order persist the value when the ISO/IEC 15118 controller is shut down for energy
* in order persist the value when the ISO 15118 controller is shut down for energy
* saving reasons.
* The initial value for a completely new charging session must be 0.
* The initial value for a completely new charging session must be 00.
*/
long sessionID = (long) MiscUtils.getPropertyValue("session.id");
getCommSessionContext().setSessionID(
getCommSessionContext().generateSessionIDFromValue(sessionID)
);
String sessionID = (String) MiscUtils.getPropertyValue("session.id");
try {
getCommSessionContext().setSessionID(ByteUtils.toByteArrayFromHexString(sessionID));
} catch (IllegalArgumentException e) {
getLogger().warn("Stored session ID '" + sessionID + "' contains illegal character(s) which are not hexadecimal. " +
"Will reset session ID to '00'");
getCommSessionContext().setSessionID(ByteUtils.toByteArrayFromHexString("00"));
}
} else {
return new TerminateSession("No supported appProtocol found (positive response code received, " +
"but no valid schemaID. Received schema ID is: " +

View File

@ -50,15 +50,13 @@ public class WaitForWeldingDetectionRes extends ClientState {
* How to react on DCEVSEStatus values?
*/
if (getCommSessionContext().isPausingV2GCommSession()) {
getCommSessionContext().setPausingV2GCommSession(false);
if (getCommSessionContext().getChargingSession() != null &&
getCommSessionContext().getChargingSession() == ChargingSessionType.PAUSE) {
return getSendMessage(getSessionStopReq(ChargingSessionType.PAUSE),
V2GMessages.SESSION_STOP_RES, "(ChargingSession = " +
ChargingSessionType.PAUSE.toString() + ")");
} else {
getCommSessionContext().setStopChargingRequested(false);
} else {
return getSendMessage(getSessionStopReq(ChargingSessionType.TERMINATE),
V2GMessages.SESSION_STOP_RES, "(ChargingSession = " +
ChargingSessionType.TERMINATE.toString() + ")");

View File

@ -73,12 +73,27 @@ public class DummyBackendInterface implements IBackendInterface {
}
@Override
public SAScheduleListType getSAScheduleList(
int maxEntriesSAScheduleTuple,
long departureTime,
HashMap<String, byte[]> xmlSignatureRefElements) {
return getSAScheduleList(maxEntriesSAScheduleTuple, departureTime, xmlSignatureRefElements, (short) -1);
}
@Override
public SAScheduleListType getSAScheduleList(
int maxEntriesSAScheduleTuple,
long departureTime,
HashMap<String, byte[]> xmlSignatureRefElements,
short selectedSAScheduleTupleId) {
/*
* This is a static list of SASchedules. This means that we always offer the same list and ignore the
* processing of the parameter selectedSAScheduleTupleId.
*
*
* Some important requirements:
*
* 1. The sum of the individual time intervals described in the PMaxSchedule and

View File

@ -40,6 +40,7 @@ public interface IBackendInterface {
*/
public void setCommSessionContext(V2GCommunicationSessionSECC commSessionContext);
/**
* Provides a list of schedules coming from a secondary actor (SAScheduleList) with pMax values
* and optional tariff incentives which shall influence the charging behaviour of the EV.
@ -47,6 +48,7 @@ public interface IBackendInterface {
* @param maxEntriesSAScheduleTuple The maximum number of PMaxEntries and SalesTariff entries allowed by EVCC
* @param departureTime The departure time provided by the EV
* @param xmlSignatureRefElements Signature reference parameter provided to put sales tariff IDs and sales tariffs in
*
* @return An SASchedulesType element with a list of secondary actor schedules
*/
public SAScheduleListType getSAScheduleList(
@ -55,6 +57,27 @@ public interface IBackendInterface {
HashMap<String, byte[]> xmlSignatureRefElements);
/**
* Provides a list of schedules coming from a secondary actor (SAScheduleList) with pMax values
* and optional tariff incentives which shall influence the charging behaviour of the EV.
*
* The parameter selectedSAScheduleTupleId tells the backend to again offer a schedule with that ID
* because the EVCC requested it in a previous charging session that has now been resumed after pausing.
*
* @param maxEntriesSAScheduleTuple The maximum number of PMaxEntries and SalesTariff entries allowed by EVCC
* @param departureTime The departure time provided by the EV
* @param xmlSignatureRefElements Signature reference parameter provided to put sales tariff IDs and sales tariffs in
* @param selectedSAScheduleTupleId The SAScheduleTupleID which the EVCC chose in a previous charging session (optional)
*
* @return An SASchedulesType element with a list of secondary actor schedules
*/
public SAScheduleListType getSAScheduleList(
int maxEntriesSAScheduleTuple,
long departureTime,
HashMap<String, byte[]> xmlSignatureRefElements,
short selectedSAScheduleTupleId);
/**
* Provides a certificate chain coming from a secondary actor with the leaf certificate being
* the contract certificate and possible intermediate certificates (Sub-CAs) included.

View File

@ -36,7 +36,7 @@ public class StartSECC {
public static void main(String[] args) {
final Logger logger = LogManager.getLogger(StartSECC.class.getSimpleName());
MiscUtils.setV2gEntityConfig(GlobalValues.SECC_CONFIG_PROPERTIES_PATH.toString());
MiscUtils.loadProperties(GlobalValues.SECC_CONFIG_PROPERTIES_PATH.toString());
UDPServer udpServer = UDPServer.getInstance();
TCPServer tcpServer = TCPServer.getInstance();

View File

@ -38,6 +38,7 @@ import com.v2gclarity.risev2g.secc.transportLayer.TLSServer;
import com.v2gclarity.risev2g.secc.transportLayer.UDPServer;
import com.v2gclarity.risev2g.shared.enumerations.GlobalValues;
import com.v2gclarity.risev2g.shared.messageHandling.MessageHandler;
import com.v2gclarity.risev2g.shared.messageHandling.PauseSession;
import com.v2gclarity.risev2g.shared.messageHandling.TerminateSession;
import com.v2gclarity.risev2g.shared.misc.V2GTPMessage;
import com.v2gclarity.risev2g.shared.utils.ByteUtils;
@ -97,13 +98,19 @@ public class V2GCommunicationSessionHandlerSECC implements Observer {
* before the V2GCommunicationSessionSECC object is instantiated, otherwise it may lead to
* race conditions.
*/
getLogger().debug("Resuming previous communication session ...");
V2GCommunicationSessionSECC continuedSession = getV2gCommunicationSessions().get(ipAddress);
// Reset charging session state from previous session (namely ChargingSessionType.PAUSE) to avoid confusion in the algorithm
continuedSession.setChargingSession(null);
continuedSession.setConnectionHandler((ConnectionHandler) obj);
continuedSession.setTlsConnection((obs instanceof TLSServer) ? true : false);
((ConnectionHandler) obj).addObserver(getV2gCommunicationSessions().get(ipAddress));
manageConnectionHandlers((ConnectionHandler) obj);
} else {
getLogger().debug("Initiating a new communication session ...");
V2GCommunicationSessionSECC newSession = new V2GCommunicationSessionSECC((ConnectionHandler) obj);
newSession.setTlsConnection((obs instanceof TLSServer) ? true : false);
newSession.addObserver(this);
@ -112,11 +119,14 @@ public class V2GCommunicationSessionHandlerSECC implements Observer {
manageConnectionHandlers((ConnectionHandler) obj);
}
} else if (obs instanceof V2GCommunicationSessionSECC && obj instanceof TerminateSession) {
// Remove the V2GCommunicationSessionSECC instance from the hashmap
// Remove the V2GCommunicationSessionSECC instance from the hash map
String ipAddress = ((V2GCommunicationSessionSECC) obs).getConnectionHandler().getAddress();
getV2gCommunicationSessions().remove(ipAddress);
stopConnectionHandler(((V2GCommunicationSessionSECC) obs).getConnectionHandler());
stopConnectionHandler(((V2GCommunicationSessionSECC) obs).getConnectionHandler(), false);
} else if (obs instanceof V2GCommunicationSessionSECC && obj instanceof PauseSession) {
// Stop the connection handler, but keep the V2GCommunicationSessionSECC instance in the hash map
stopConnectionHandler(((V2GCommunicationSessionSECC) obs).getConnectionHandler(), true);
} else {
getLogger().warn("Notification received, but sending entity or received object not identifiable");
}
@ -184,7 +194,7 @@ public class V2GCommunicationSessionHandlerSECC implements Observer {
* @param connectionHandler The ConnectionHandler whose socket is to be closed and whose thread
* is to be interrupted.
*/
public void stopConnectionHandler(ConnectionHandler connectionHandler) {
public void stopConnectionHandler(ConnectionHandler connectionHandler, boolean pausingSession) {
if (getConnectionHandlerMap().containsKey(connectionHandler)) {
// Close the socket
connectionHandler.stop();
@ -196,7 +206,9 @@ public class V2GCommunicationSessionHandlerSECC implements Observer {
// Remove HashMap entry
getConnectionHandlerMap().remove(connectionHandler);
getLogger().debug("Thread '" + connectionThread.getName() + "' has been interrupted and removed\n\n" );
getLogger().debug("Thread '" + connectionThread.getName() + "' has been interrupted and removed" +
((pausingSession) ? ". Charging session is paused." : "") + "\n\n");
} else {
String address = connectionHandler.getAddress();
int port = connectionHandler.getPort();

View File

@ -57,20 +57,24 @@ import com.v2gclarity.risev2g.secc.transportLayer.ConnectionHandler;
import com.v2gclarity.risev2g.shared.enumerations.GlobalValues;
import com.v2gclarity.risev2g.shared.enumerations.V2GMessages;
import com.v2gclarity.risev2g.shared.messageHandling.ChangeProcessingState;
import com.v2gclarity.risev2g.shared.messageHandling.PauseSession;
import com.v2gclarity.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import com.v2gclarity.risev2g.shared.messageHandling.SendMessage;
import com.v2gclarity.risev2g.shared.messageHandling.TerminateSession;
import com.v2gclarity.risev2g.shared.misc.V2GCommunicationSession;
import com.v2gclarity.risev2g.shared.misc.V2GTPMessage;
import com.v2gclarity.risev2g.shared.utils.ByteUtils;
import com.v2gclarity.risev2g.shared.utils.MiscUtils;
import com.v2gclarity.risev2g.shared.v2gMessages.appProtocol.SupportedAppProtocolReq;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ACEVSEStatusType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.CertificateChainType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargingSessionType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.EVSENotificationType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.EnergyTransferModeType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.MessageHeaderType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.MeterInfoType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PMaxScheduleType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PaymentOptionListType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PaymentOptionType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ResponseCodeType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.SAScheduleListType;
@ -81,8 +85,7 @@ public class V2GCommunicationSessionSECC extends V2GCommunicationSession impleme
private short schemaID;
private ACEVSEStatusType acEVSEStatus;
private boolean stopV2GCommunicationSession;
private boolean pauseV2GCommunicationSession;
private ChargingSessionType chargingSession;
private PMaxScheduleType pMaxSchedule;
private short chosenSAScheduleTuple;
private IACEVSEController acEvseController;
@ -142,8 +145,8 @@ public class V2GCommunicationSessionSECC extends V2GCommunicationSession impleme
getACEVSEStatus().setNotificationMaxDelay(0);
getACEVSEStatus().setRCD(false);
setStopV2GCommunicationSession(false);
setPauseV2GCommunicationSession(false);
// Will be set only if a session is to be stopped or paused
setChargingSession(null);
setOfferedServices(new ArrayList<ServiceType>());
@ -189,8 +192,11 @@ public class V2GCommunicationSessionSECC extends V2GCommunicationSession impleme
// Check the outcome of the processIncomingMessage() of the respective state
if (reactionToIncomingMessage instanceof SendMessage) {
send((SendMessage) reactionToIncomingMessage);
if (isStopV2GCommunicationSession()) {
if (getChargingSession() != null && getChargingSession() == ChargingSessionType.TERMINATE)
terminateSession("EVCC indicated request to stop the session or a FAILED response code was sent", true);
if (getChargingSession() != null && getChargingSession() == ChargingSessionType.PAUSE) {
pauseSession(new PauseSession());
}
} else if (reactionToIncomingMessage instanceof ChangeProcessingState) {
setCurrentState(((ChangeProcessingState) reactionToIncomingMessage).getNewState());
@ -217,20 +223,22 @@ public class V2GCommunicationSessionSECC extends V2GCommunicationSession impleme
*/
public ResponseCodeType checkSessionID(MessageHeaderType header) {
if (getCurrentState().equals(getStates().get(V2GMessages.SESSION_SETUP_REQ)) &&
ByteUtils.toLongFromByteArray(header.getSessionID()) == 0L) {
ByteUtils.toHexString(header.getSessionID()).equals("00")) {
// EV wants to start a totally new charging session
setSessionID(generateSessionIDRandomly());
setOldSessionJoined(false);
return ResponseCodeType.OK_NEW_SESSION_ESTABLISHED;
} else if (getCurrentState().equals(getStates().get(V2GMessages.SESSION_SETUP_REQ)) &&
header.getSessionID() == getSessionID()) {
Arrays.equals(header.getSessionID(), getSessionID())) {
// A charging pause has taken place and the EV wants to resume the old charging session
setOldSessionJoined(true);
return ResponseCodeType.OK_OLD_SESSION_JOINED;
} else if (getCurrentState().equals(getStates().get(V2GMessages.SESSION_SETUP_REQ)) &&
ByteUtils.toLongFromByteArray(header.getSessionID()) != 0L &&
header.getSessionID() != getSessionID()) {
!ByteUtils.toHexString(header.getSessionID()).equals("00") &&
!Arrays.equals(header.getSessionID(), getSessionID())) {
// Avoid a "FAILED_..." response code by generating a new SessionID in the response
getLogger().warn("Presented session ID '" + ByteUtils.toHexString(header.getSessionID()) + "' does not match stored session ID '" +
ByteUtils.toHexString(getSessionID()) + "'. Will reassign a new session ID");
setSessionID(generateSessionIDRandomly());
setOldSessionJoined(false);
return ResponseCodeType.OK_NEW_SESSION_ESTABLISHED;
@ -246,6 +254,27 @@ public class V2GCommunicationSessionSECC extends V2GCommunicationSession impleme
}
@SuppressWarnings("unchecked")
public PaymentOptionListType getPaymentOptions() {
ArrayList<PaymentOptionType> paymentOptions = new ArrayList<PaymentOptionType>();
if (isOldSessionJoined()) {
paymentOptions.add(selectedPaymentOption);
} else {
paymentOptions.addAll((ArrayList<PaymentOptionType>) (MiscUtils.getPropertyValue("authentication.modes.supported")));
}
// Contract-based payment may only be offered if TLS is used
if (!isTlsConnection())
paymentOptions.remove(PaymentOptionType.CONTRACT);
PaymentOptionListType paymentOptionList = new PaymentOptionListType();
paymentOptionList.getPaymentOption().addAll(paymentOptions);
return paymentOptionList;
}
public void send(SendMessage sendMessage) {
// Only EXI encoded messages will be sent here. Decide whether V2GMessage or SupportedAppProtocolRes
byte[] payload = null;
@ -284,24 +313,6 @@ public class V2GCommunicationSessionSECC extends V2GCommunicationSession impleme
return acEVSEStatus;
}
public boolean isStopV2GCommunicationSession() {
return stopV2GCommunicationSession;
}
public void setStopV2GCommunicationSession(boolean stopV2GCommunicationSession) {
this.stopV2GCommunicationSession = stopV2GCommunicationSession;
}
public boolean isPauseV2GCommunicationSession() {
return pauseV2GCommunicationSession;
}
public void setPauseV2GCommunicationSession(boolean pauseV2GCommunicationSession) {
this.pauseV2GCommunicationSession = pauseV2GCommunicationSession;
}
public PMaxScheduleType getPMaxSchedule() {
return pMaxSchedule;
}
@ -475,4 +486,14 @@ public class V2GCommunicationSessionSECC extends V2GCommunicationSession impleme
public void setChargeProgressStarted(boolean chargeProgressStarted) {
this.chargeProgressStarted = chargeProgressStarted;
}
public ChargingSessionType getChargingSession() {
return chargingSession;
}
public void setChargingSession(ChargingSessionType chargingSession) {
this.chargingSession = chargingSession;
}
}

View File

@ -38,6 +38,7 @@ import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.CertificateChainType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.CertificateInstallationResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.CertificateUpdateResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargeParameterDiscoveryResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargingSessionType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargingStatusResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ContractSignatureEncryptedPrivateKeyType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.CurrentDemandResType;
@ -167,7 +168,7 @@ public abstract class ServerState extends State {
if (!responseCode.value().startsWith("OK")) {
getLogger().error("Response code '" + responseCode.value() + "' will be sent.");
getCommSessionContext().setStopV2GCommunicationSession(true);
getCommSessionContext().setChargingSession(ChargingSessionType.TERMINATE);
}
return getSendMessage(message, nextExpectedMessage, "", timeout);
@ -181,7 +182,7 @@ public abstract class ServerState extends State {
if (!responseCode.value().substring(0, 2).toUpperCase().equals("OK")) {
getLogger().error("Response code '" + responseCode.value() + "' will be sent.");
getCommSessionContext().setStopV2GCommunicationSession(true);
getCommSessionContext().setChargingSession(ChargingSessionType.TERMINATE);
}
getLogger().debug("Preparing to send " + messageName);

View File

@ -40,6 +40,7 @@ import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargeParameterDiscovery
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.DCEVChargeParameterType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.EVSEProcessingType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.EnergyTransferModeType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.IsolationLevelType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ResponseCodeType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.SAScheduleListType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.V2GMessage;
@ -80,12 +81,22 @@ public class WaitForChargeParameterDiscoveryReq extends ServerState {
Long departureTime = chargeParameterDiscoveryReq.getEVChargeParameter().getValue().getDepartureTime();
getCommSessionContext().setSaSchedules(
getCommSessionContext().getBackendInterface().getSAScheduleList(
maxEntriesSAScheduleTuple,
(departureTime != null) ? departureTime.longValue() : 0,
getXMLSignatureRefElements())
);
if (getCommSessionContext().isOldSessionJoined()) {
getCommSessionContext().setSaSchedules(
getCommSessionContext().getBackendInterface().getSAScheduleList(
maxEntriesSAScheduleTuple,
(departureTime != null) ? departureTime.longValue() : 0,
getXMLSignatureRefElements(),
getCommSessionContext().getChosenSAScheduleTuple())
);
} else {
getCommSessionContext().setSaSchedules(
getCommSessionContext().getBackendInterface().getSAScheduleList(
maxEntriesSAScheduleTuple,
(departureTime != null) ? departureTime.longValue() : 0,
getXMLSignatureRefElements())
);
}
}
// TODO An integration to a backend system which provides the SalesTariff would be needed here

View File

@ -65,10 +65,6 @@ public class WaitForPowerDeliveryReq extends ServerState {
if (isResponseCodeOK(powerDeliveryReq)) {
getCommSessionContext().setChosenSAScheduleTuple(powerDeliveryReq.getSAScheduleTupleID());
// For debugging purposes, log the ChargeProgress value
getLogger().debug("ChargeProgress of PowerDeliveryReq set to '" +
powerDeliveryReq.getChargeProgress().toString() + "'");
// TODO regard [V2G2-866]
setEVSEStatus(powerDeliveryRes);
@ -114,8 +110,9 @@ public class WaitForPowerDeliveryReq extends ServerState {
public boolean isResponseCodeOK(PowerDeliveryReqType powerDeliveryReq) {
SAScheduleTupleType chosenSASchedule = getChosenSASCheduleTuple(powerDeliveryReq.getSAScheduleTupleID());
// This debug message is helpful to determine why the EV might not send a ChargingProfile (parameter is optional and should only be left out if ChargeProgress is set to Stop)
getLogger().debug("ChargeProgress is set to " + powerDeliveryReq.getChargeProgress());
// This debug message is helpful to determine why the EV might not send a ChargingProfile
// (parameter is optional and should only be left out if ChargeProgress is set to Stop)
getLogger().debug("ChargeProgress of PowerDeliveryReq set to '" + powerDeliveryReq.getChargeProgress().toString() + "'");
if (powerDeliveryReq.getChargeProgress().equals(ChargeProgressType.RENEGOTIATE) &&
!getCommSessionContext().isChargeProgressStarted()) {

View File

@ -27,6 +27,7 @@ import com.v2gclarity.risev2g.secc.session.V2GCommunicationSessionSECC;
import com.v2gclarity.risev2g.shared.enumerations.V2GMessages;
import com.v2gclarity.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.BodyBaseType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargingSessionType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PaymentServiceSelectionReqType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ResponseCodeType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.SessionStopReqType;
@ -51,18 +52,26 @@ public class WaitForSessionStopReq extends ServerState {
getLogger().info("EV indicated to " + sessionStopReq.getChargingSession() + " the charging session");
getCommSessionContext().setStopV2GCommunicationSession(true);
if (sessionStopReq.getChargingSession() == ChargingSessionType.TERMINATE) {
getCommSessionContext().setChargingSession(ChargingSessionType.TERMINATE);
return getSendMessage(sessionStopRes, V2GMessages.NONE, sessionStopRes.getResponseCode());
} else {
// EV indicated to pause the charging session. Next expected request message is SupportedAppProtocolReq
getCommSessionContext().setChargingSession(ChargingSessionType.PAUSE);
return getSendMessage(sessionStopRes, V2GMessages.SUPPORTED_APP_PROTOCOL_REQ, sessionStopRes.getResponseCode());
}
} else {
getCommSessionContext().setChargingSession(ChargingSessionType.TERMINATE);
if (sessionStopRes.getResponseCode().equals(ResponseCodeType.FAILED_SEQUENCE_ERROR)) {
BodyBaseType responseMessage = getSequenceErrorResMessage(new SessionStopResType(), message);
return getSendMessage(responseMessage, V2GMessages.NONE, sessionStopRes.getResponseCode());
} else {
setMandatoryFieldsForFailedRes(sessionStopRes, sessionStopRes.getResponseCode());
return getSendMessage(sessionStopRes, V2GMessages.NONE, sessionStopRes.getResponseCode());
}
}
return getSendMessage(sessionStopRes, V2GMessages.NONE, sessionStopRes.getResponseCode());
}

View File

@ -60,16 +60,7 @@ public abstract class V2GCommunicationSession extends Observable {
setSessionID(null);
setV2gTpMessage(null);
}
/**
* Generates a session ID (with length of 8 bytes) from a given long value.
* @param The long value representing a session ID (either 0 or a previously stored session ID)
* @return The byte array representation of the provided session ID
*/
public byte[] generateSessionIDFromValue(long fromValue) {
return ByteUtils.toByteArrayFromLong(fromValue);
}
/**
* Generates randomly a new session ID (with length of 8 bytes) and takes care that the newly generated
@ -87,8 +78,7 @@ public abstract class V2GCommunicationSession extends Observable {
}
protected void pauseSession(PauseSession pauseObject) {
getLogger().info("Pausing"
+ " V2G communication session");
getLogger().info("Pausing V2G communication session");
setChanged();
notifyObservers(pauseObject);
}
@ -126,25 +116,6 @@ public abstract class V2GCommunicationSession extends Observable {
}
public PaymentOptionListType getPaymentOptions() {
@SuppressWarnings("unchecked")
ArrayList<PaymentOptionType> paymentOptions = (ArrayList<PaymentOptionType>) (MiscUtils.getPropertyValue("authentication.modes.supported"));
if (paymentOptions == null) {
paymentOptions = new ArrayList<PaymentOptionType>();
}
// Contract-based payment may only be offered if TLS is used
if (!isTlsConnection())
paymentOptions.remove(PaymentOptionType.CONTRACT);
PaymentOptionListType paymentOptionList = new PaymentOptionListType();
paymentOptionList.getPaymentOption().addAll(paymentOptions);
return paymentOptionList;
}
public ArrayList<EnergyTransferModeType> getSupportedEnergyTransferModes() {
@SuppressWarnings("unchecked")
ArrayList<EnergyTransferModeType> energyTransferModes =
@ -199,10 +170,9 @@ public abstract class V2GCommunicationSession extends Observable {
public void setSessionID(byte[] sessionID) {
if (sessionID == null) {
sessionID = generateSessionIDFromValue(0L);
sessionID = ByteUtils.toByteArrayFromHexString("00");
}
this.sessionID = sessionID;
MiscUtils.getV2gEntityConfig().setProperty("session.id", String.valueOf(ByteUtils.toLongFromByteArray(sessionID)));
}
public V2GTPMessage getV2gTpMessage() {

View File

@ -43,7 +43,7 @@ public abstract class V2GImplementationFactory {
*/
protected static <T> T buildFromProperties(String propertyName, Class<T> cls) {
try {
String className = MiscUtils.getV2gEntityConfig().getProperty(propertyName);
String className = MiscUtils.getProperties().getProperty(propertyName);
if (className == null) {
return null;
}

View File

@ -67,9 +67,11 @@ public final class ByteUtils {
* representing the octet code. For example, '0FB7' is a hex encoding for the
* 16-bit integer 4023 (whose binary representation is 111110110111)."
* @return A byte array representing the hexadecimal string
* @throws IllegalArgumentException Passes on the IllegalArgumentException from the method DatatypeConverter.parseHexBinary()
* so that the caller can appropriately handle the execption
*/
public static byte[] toByteArrayFromHexString(String s) {
return DatatypeConverter.parseHexBinary(s);
public static byte[] toByteArrayFromHexString(String s) throws IllegalArgumentException {
return DatatypeConverter.parseHexBinary(s);
}
/**

View File

@ -25,6 +25,7 @@ package com.v2gclarity.risev2g.shared.utils;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.Inet6Address;
import java.net.InetAddress;
@ -47,7 +48,7 @@ public final class MiscUtils {
static Logger logger = LogManager.getLogger(MiscUtils.class.getSimpleName());
static V2GMessages[] messageTypes = V2GMessages.values();
static Properties v2gEntityConfig;
static Properties properties;
public static Logger getLogger() {
return logger;
@ -131,34 +132,27 @@ public final class MiscUtils {
* in this method. The return value differs depending on the key. Therefore, the return value
* is given as an Object, which again must be casted to the matching type when using this method.
*
* @param propertyName The key string written in the respective properties file of each V2G entity (EV or EVSE)
* @param propertyKey The key string written in the respective properties file of each V2G entity (EV or EVSE)
* @return An Object holding the data structure fitting for the key (e.g. an Enum value, a Boolean,
* a collection, ...)
*/
public static Object getPropertyValue(String propertyName) {
public static Object getPropertyValue(String propertyKey) {
Object returnValue = null;
String propertyValue = "";
try {
propertyValue = getV2gEntityConfig().getProperty(propertyName).replaceAll("\\s", "");
propertyValue = getProperties().getProperty(propertyKey).replaceAll("\\s", "");
} catch (NullPointerException e) {
getLogger().warn("No entry found in the properties file for property '" + propertyName + "'", e);
getLogger().warn("No entry found in the properties file for property '" + propertyKey + "'", e);
return null;
}
switch (propertyName) {
switch (propertyKey) {
case "network.interface": // EV + EVSE property
returnValue = propertyValue;
break;
case "session.id": // EV property
try {
returnValue = Long.parseLong(propertyValue);
} catch (NumberFormatException e) {
getLogger().warn("SessionID '" + propertyValue + "' not supported. " +
"Setting default value to 0.", e);
getV2gEntityConfig().setProperty("session.id", "0");
returnValue = 0L;
}
returnValue = propertyValue; // a hexadecimal string representing a byte array
break;
case "energy.transfermodes.supported": // EVSE property
String energyTransferMode = "";
@ -170,7 +164,7 @@ public final class MiscUtils {
try {
supportedEnergyTransferModeType.getEnergyTransferMode().add(EnergyTransferModeType.fromValue(energyTransferMode));
} catch (IllegalArgumentException e){
getLogger().warn("EnergyTransferModeType '" + energyTransferMode + "' not supported");
getLogger().warn("EnergyTransferModeType '" + energyTransferMode + "' listed in properties file is not supported");
}
}
}
@ -178,11 +172,10 @@ public final class MiscUtils {
break;
case "energy.transfermode.requested": // EV property
try {
if (!propertyValue.equals("")) returnValue = EnergyTransferModeType.fromValue(propertyValue);
else return null;
if (!propertyValue.equals(""))
returnValue = EnergyTransferModeType.fromValue(propertyValue);
} catch (IllegalArgumentException e) {
getLogger().warn("EnergyTransferModeType '" + propertyValue + "' not supported");
return null;
getLogger().warn("EnergyTransferModeType '" + propertyValue + "' listed in properties file is not supported");
}
break;
case "tls": // EV property (with this code, TLS is always supported on EVSE side)
@ -193,19 +186,18 @@ public final class MiscUtils {
try {
returnValue = Integer.parseInt(propertyValue);
} catch (NumberFormatException e) {
getLogger().warn("ContractCertificateUpdateTimespan '" + propertyValue + "' not supported. " +
getLogger().warn("ContractCertificateUpdateTimespan '" + propertyValue + "' listed in properties file is not supported. " +
"Setting default value to 14.", e);
getV2gEntityConfig().setProperty("contract.certificate.update.timespan", "14");
getProperties().setProperty("contract.certificate.update.timespan", "14");
returnValue = 14;
}
break;
case "authentication.mode": // EV property
try {
if (!propertyValue.equals("")) returnValue = PaymentOptionType.fromValue(propertyValue);
else return null;
if (!propertyValue.equals(""))
returnValue = PaymentOptionType.fromValue(propertyValue);
} catch (IllegalArgumentException e) {
getLogger().warn("PaymentOptionType '" + propertyValue + "' not supported");
return null;
getLogger().warn("PaymentOptionType '" + propertyValue + "' listed in properties file is not supported");
}
break;
case "authentication.modes.supported": // EVSE property
@ -219,7 +211,7 @@ public final class MiscUtils {
try {
paymentOptionsList.add(PaymentOptionType.fromValue(option));
} catch (IllegalArgumentException e) {
getLogger().warn("PaymentOptionType '" + option + "' not supported");
getLogger().warn("PaymentOptionType '" + option + "' listed in properties file is not supported");
}
}
}
@ -251,14 +243,14 @@ public final class MiscUtils {
try {
returnValue = Integer.parseInt(propertyValue);
} catch (NumberFormatException e) {
getLogger().warn("Voltage accuracy '" + propertyValue + "' not supported. " +
getLogger().warn("Voltage accuracy '" + propertyValue + "' listed in properties file is not supported. " +
"Setting default value to 5.", e);
getV2gEntityConfig().setProperty("voltage.accuracy", "5");
getProperties().setProperty("voltage.accuracy", "5");
returnValue = 5;
}
break;
default:
getLogger().error("No property with name '" + propertyName + "' found");
getLogger().error("No property with name '" + propertyKey + "' found");
}
return returnValue;
@ -273,8 +265,8 @@ public final class MiscUtils {
* @return The Properties object containing the (key, value)-pairs of the respective properties
* file for the respective V2G entity (EVCC or SECC)
*/
public static Properties getV2gEntityConfig() {
return v2gEntityConfig;
public static Properties getProperties() {
return properties;
}
@ -287,13 +279,12 @@ public final class MiscUtils {
* @param propertiesFileLocation The location of the properties file
* @return True, if the properties file could be loaded successfully.
*/
public static boolean setV2gEntityConfig(String propertiesFileLocation) {
Properties properties = new Properties();
public static boolean loadProperties(String propertiesFileLocation) {
properties = new Properties();
try {
FileInputStream config = new FileInputStream(propertiesFileLocation);
properties.load(config);
v2gEntityConfig = properties;
config.close();
return true;
} catch (FileNotFoundException e) {
@ -306,4 +297,25 @@ public final class MiscUtils {
return false;
}
}
/**
* Stores the current properties into the properties file
*
*/
public static void storeProperties(String propertiesFileLocation) {
try {
FileOutputStream configFile = new FileOutputStream(propertiesFileLocation);
getProperties().store(configFile, "");
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// getV2gEntityConfig().store(out, "");
}
}