From cba5e041f6d14f1c928dd88955d0a23c350323b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20Mu=CC=88ltin?= Date: Wed, 25 Apr 2018 10:14:34 +0200 Subject: [PATCH] - TargetCurrent in DummyEVController, used in PreChargeReq message, was set to 2A to comply to IEC 61851-23 - The necessary change from State C to State B during a renegotiation in DC charging is now correctly implemented - Added the EV setting "voltage.accuracy" to allow for a percentage of deviation from the target current in PreCharge --- RISE-V2G-EVCC/EVCCConfig.properties | 9 ++++++ .../evcc/evController/DummyEVController.java | 2 +- .../evcc/states/WaitForCurrentDemandRes.java | 1 + .../evcc/states/WaitForPowerDeliveryRes.java | 5 ++++ .../evcc/states/WaitForPreChargeRes.java | 7 ++++- .../states/WaitForServiceDiscoveryRes.java | 2 +- .../secc/backend/DummyBackendInterface.java | 5 ++-- .../secc/backend/IBackendInterface.java | 8 ++++++ .../evseController/DummyACEVSEController.java | 5 ---- .../secc/states/WaitForPowerDeliveryReq.java | 8 ++++-- .../shared/enumerations/GlobalValues.java | 2 +- .../risev2g/shared/misc/TimeRestrictions.java | 15 ---------- .../risev2g/shared/utils/MiscUtils.java | 28 +++++++++++++------ 13 files changed, 61 insertions(+), 36 deletions(-) diff --git a/RISE-V2G-EVCC/EVCCConfig.properties b/RISE-V2G-EVCC/EVCCConfig.properties index 94f0dc0c..53aeb675 100644 --- a/RISE-V2G-EVCC/EVCCConfig.properties +++ b/RISE-V2G-EVCC/EVCCConfig.properties @@ -126,3 +126,12 @@ signature.verification.showlog = true # - open_exi # If no correct value is provided here, 'exificient' will be used exi.codec = exificient + + +# Voltage accuracy +#---------- +# +# 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 diff --git a/RISE-V2G-EVCC/src/main/java/com/v2gclarity/risev2g/evcc/evController/DummyEVController.java b/RISE-V2G-EVCC/src/main/java/com/v2gclarity/risev2g/evcc/evController/DummyEVController.java index 246afce6..d94e8e98 100644 --- a/RISE-V2G-EVCC/src/main/java/com/v2gclarity/risev2g/evcc/evController/DummyEVController.java +++ b/RISE-V2G-EVCC/src/main/java/com/v2gclarity/risev2g/evcc/evController/DummyEVController.java @@ -228,7 +228,7 @@ public PhysicalValueType getTargetCurrent() { PhysicalValueType targetCurrent = new PhysicalValueType(); targetCurrent.setMultiplier(new Byte("0")); targetCurrent.setUnit(UnitSymbolType.A); - targetCurrent.setValue((short) 32); + targetCurrent.setValue((short) 2); // according to IEC 61851-23, this value should be limited to 2A as it seems (see https://github.com/V2GClarity/RISE-V2G/issues/20) return targetCurrent; } diff --git a/RISE-V2G-EVCC/src/main/java/com/v2gclarity/risev2g/evcc/states/WaitForCurrentDemandRes.java b/RISE-V2G-EVCC/src/main/java/com/v2gclarity/risev2g/evcc/states/WaitForCurrentDemandRes.java index 928b664b..a8ce08b8 100644 --- a/RISE-V2G-EVCC/src/main/java/com/v2gclarity/risev2g/evcc/states/WaitForCurrentDemandRes.java +++ b/RISE-V2G-EVCC/src/main/java/com/v2gclarity/risev2g/evcc/states/WaitForCurrentDemandRes.java @@ -92,6 +92,7 @@ public ReactionToIncomingMessage processIncomingMessage(Object message) { V2GMessages.POWER_DELIVERY_RES, " (ChargeProgress = STOP_CHARGING)"); case RE_NEGOTIATION: + getCommSessionContext().setRenegotiationRequested(true); return getSendMessage(getPowerDeliveryReq(ChargeProgressType.RENEGOTIATE), V2GMessages.POWER_DELIVERY_RES, " (ChargeProgress = RE_NEGOTIATION)"); diff --git a/RISE-V2G-EVCC/src/main/java/com/v2gclarity/risev2g/evcc/states/WaitForPowerDeliveryRes.java b/RISE-V2G-EVCC/src/main/java/com/v2gclarity/risev2g/evcc/states/WaitForPowerDeliveryRes.java index c1fd2340..ac382bb8 100644 --- a/RISE-V2G-EVCC/src/main/java/com/v2gclarity/risev2g/evcc/states/WaitForPowerDeliveryRes.java +++ b/RISE-V2G-EVCC/src/main/java/com/v2gclarity/risev2g/evcc/states/WaitForPowerDeliveryRes.java @@ -61,6 +61,11 @@ public ReactionToIncomingMessage processIncomingMessage(Object message) { */ if (getCommSessionContext().isRenegotiationRequested()) { + // In DC charging, we need to switch to state B during renegotiation because we need to go through CableCheckReq and PreChargeReq again for which state B is required + if (getCommSessionContext().getRequestedEnergyTransferMode().toString().startsWith("DC")) { + getCommSessionContext().setChangeToState(CPStates.STATE_B); + } + getCommSessionContext().setRenegotiationRequested(false); return getSendMessage(getChargeParameterDiscoveryReq(), V2GMessages.CHARGE_PARAMETER_DISCOVERY_RES); } else if (getCommSessionContext().isStopChargingRequested()) { diff --git a/RISE-V2G-EVCC/src/main/java/com/v2gclarity/risev2g/evcc/states/WaitForPreChargeRes.java b/RISE-V2G-EVCC/src/main/java/com/v2gclarity/risev2g/evcc/states/WaitForPreChargeRes.java index 384d8fe5..7a78f06b 100644 --- a/RISE-V2G-EVCC/src/main/java/com/v2gclarity/risev2g/evcc/states/WaitForPreChargeRes.java +++ b/RISE-V2G-EVCC/src/main/java/com/v2gclarity/risev2g/evcc/states/WaitForPreChargeRes.java @@ -31,6 +31,7 @@ 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.MiscUtils; import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargeProgressType; import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PreChargeReqType; import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PreChargeResType; @@ -55,7 +56,10 @@ public ReactionToIncomingMessage processIncomingMessage(Object message) { double targetVoltage = dcEvController.getTargetVoltage().getValue() * Math.pow(10, dcEvController.getTargetVoltage().getMultiplier()); double presentVoltage = preChargeRes.getEVSEPresentVoltage().getValue() * Math.pow(10, preChargeRes.getEVSEPresentVoltage().getMultiplier()); - if (targetVoltage == presentVoltage) { + // Each EV has an EV-specific allowed deviation when measuring a target voltage + int voltageAccuracy = (int) MiscUtils.getPropertyValue("voltage.accuracy"); + + if (presentVoltage >= targetVoltage * (1 - voltageAccuracy / 100) && presentVoltage <= targetVoltage * (1 + voltageAccuracy / 100)) { getCommSessionContext().setOngoingTimerActive(false); getCommSessionContext().setOngoingTimer(0L); @@ -67,6 +71,7 @@ public ReactionToIncomingMessage processIncomingMessage(Object message) { if (elapsedTimeInMs > TimeRestrictions.V2G_EVCC_PRE_CHARGE_TIMEOUT) return new TerminateSession("PreCharge timer timed out for PreChargeReq"); else { + getLogger().debug("Target voltage of " + targetVoltage + " V not yet reached. Present voltage at EVSE is " + presentVoltage); PreChargeReqType preChargeReq = new PreChargeReqType(); preChargeReq.setDCEVStatus(dcEvController.getDCEVStatus()); preChargeReq.setEVTargetCurrent(dcEvController.getTargetCurrent()); diff --git a/RISE-V2G-EVCC/src/main/java/com/v2gclarity/risev2g/evcc/states/WaitForServiceDiscoveryRes.java b/RISE-V2G-EVCC/src/main/java/com/v2gclarity/risev2g/evcc/states/WaitForServiceDiscoveryRes.java index 877b86b1..402d8842 100644 --- a/RISE-V2G-EVCC/src/main/java/com/v2gclarity/risev2g/evcc/states/WaitForServiceDiscoveryRes.java +++ b/RISE-V2G-EVCC/src/main/java/com/v2gclarity/risev2g/evcc/states/WaitForServiceDiscoveryRes.java @@ -73,7 +73,7 @@ public ReactionToIncomingMessage processIncomingMessage(Object message) { getCommSessionContext().getOfferedServices().getService().add(serviceDiscoveryRes.getChargeService()); addSelectedService(1, null); // Assumption: a charge service is always used } else { - return new TerminateSession("Offered EnergyTransferModes not compatible with the requested one"); + return new TerminateSession("Offered EnergyTransferModes not compatible with the requested one, which is " + requestedEnergyTransferMode.toString()); } } else return new TerminateSession("No charge service available"); diff --git a/RISE-V2G-SECC/src/main/java/com/v2gclarity/risev2g/secc/backend/DummyBackendInterface.java b/RISE-V2G-SECC/src/main/java/com/v2gclarity/risev2g/secc/backend/DummyBackendInterface.java index 2e7f4cea..6073b84b 100644 --- a/RISE-V2G-SECC/src/main/java/com/v2gclarity/risev2g/secc/backend/DummyBackendInterface.java +++ b/RISE-V2G-SECC/src/main/java/com/v2gclarity/risev2g/secc/backend/DummyBackendInterface.java @@ -68,7 +68,7 @@ public DummyBackendInterface(V2GCommunicationSessionSECC commSessionContext) { */ ECPrivateKey privateKey = SecurityUtils.getPrivateKey("./moSubCA2.pkcs8.der"); if (privateKey == null) - getLogger().error("No private key available from MO Sub-CA 2 PKCS#8 file"); + getLogger().warn("No private key available from MO Sub-CA 2 PKCS#8 file. Signing a SalesTariff will therefore not be possible"); else setMoSubCA2PrivateKey(privateKey); @@ -152,7 +152,7 @@ public SAScheduleListType getSAScheduleList( SAScheduleTupleType saScheduleTuple = new SAScheduleTupleType(); saScheduleTuple.setSAScheduleTupleID((short) 1); saScheduleTuple.setPMaxSchedule(pMaxSchedule); - saScheduleTuple.setSalesTariff(salesTariff); + saScheduleTuple.setSalesTariff(salesTariff); SAScheduleListType saScheduleList = new SAScheduleListType(); saScheduleList.getSAScheduleTuple().add(saScheduleTuple); @@ -235,6 +235,7 @@ public CertificateChainType getContractCertificateChain(CertificateChainType old */ ArrayList authorizedEMAIDs = new ArrayList(); + // This is a list of EMAIDs used for testing purposes, like a whitelist EMAIDType authorizedEMAID1 = new EMAIDType(); authorizedEMAID1.setId("id1"); authorizedEMAID1.setValue("DE1ABCD2EF357A"); diff --git a/RISE-V2G-SECC/src/main/java/com/v2gclarity/risev2g/secc/backend/IBackendInterface.java b/RISE-V2G-SECC/src/main/java/com/v2gclarity/risev2g/secc/backend/IBackendInterface.java index a8cfdbd9..58588f84 100644 --- a/RISE-V2G-SECC/src/main/java/com/v2gclarity/risev2g/secc/backend/IBackendInterface.java +++ b/RISE-V2G-SECC/src/main/java/com/v2gclarity/risev2g/secc/backend/IBackendInterface.java @@ -51,6 +51,8 @@ public SAScheduleListType getSAScheduleList( * Provides a certificate chain coming from a secondary actor with the leaf certificate being * the contract certificate and possible intermediate certificates (Sub-CAs) included. * + * This interface is to be used for the CertificateUpdate + * * @param oldContractCertificateChain The to-be-updated contract certificate chain * @return Certificate chain for contract certificate */ @@ -61,6 +63,8 @@ public SAScheduleListType getSAScheduleList( * Provides a certificate chain coming from a secondary actor with the leaf certificate being * the contract certificate and possible intermediate certificates (Sub-CAs) included. * + * This interface is to be used for the CertificateInstallation + * * @param oemProvisioningCert The OEM provisioning certificate * @return Certificate chain for contract certificate */ @@ -69,6 +73,7 @@ public SAScheduleListType getSAScheduleList( /** * Provides the private key belonging to the contract certificate. + * * @return PrivateKey of the contract certificate */ public ECPrivateKey getContractCertificatePrivateKey(); @@ -77,6 +82,7 @@ public SAScheduleListType getSAScheduleList( /** * Provides a certificate chain coming from a secondary actor with the leaf certificate being * the provisioning certificate and possible intermediate certificates (sub CAs) included. + * * @return Certificate chain for provisioning certificate */ public CertificateChainType getCPSCertificateChain(); @@ -84,6 +90,7 @@ public SAScheduleListType getSAScheduleList( /** * Provides the private key belonging to the SA provisioning certificate. + * * @return PrivateKey of the SA provisioning certificate */ public ECPrivateKey getCPSLeafPrivateKey(); @@ -91,6 +98,7 @@ public SAScheduleListType getSAScheduleList( /** * Provides the private key belonging to the MO Sub-CA 2 certificate (signature of SalesTariff). + * * @return PrivateKey of the MO Sub-CA 2 certificate */ public ECPrivateKey getMOSubCA2PrivateKey(); diff --git a/RISE-V2G-SECC/src/main/java/com/v2gclarity/risev2g/secc/evseController/DummyACEVSEController.java b/RISE-V2G-SECC/src/main/java/com/v2gclarity/risev2g/secc/evseController/DummyACEVSEController.java index c9c915b5..ba07b1db 100644 --- a/RISE-V2G-SECC/src/main/java/com/v2gclarity/risev2g/secc/evseController/DummyACEVSEController.java +++ b/RISE-V2G-SECC/src/main/java/com/v2gclarity/risev2g/secc/evseController/DummyACEVSEController.java @@ -86,11 +86,6 @@ public ACEVSEStatusType getACEVSEStatus(EVSENotificationType notification) { } - public V2GCommunicationSessionSECC getCommSessionContext() { - return commSessionContext; - } - - public void setCommSessionContext(V2GCommunicationSessionSECC commSessionContext) { this.commSessionContext = commSessionContext; } diff --git a/RISE-V2G-SECC/src/main/java/com/v2gclarity/risev2g/secc/states/WaitForPowerDeliveryReq.java b/RISE-V2G-SECC/src/main/java/com/v2gclarity/risev2g/secc/states/WaitForPowerDeliveryReq.java index ecb3d1ff..de2bfad1 100644 --- a/RISE-V2G-SECC/src/main/java/com/v2gclarity/risev2g/secc/states/WaitForPowerDeliveryReq.java +++ b/RISE-V2G-SECC/src/main/java/com/v2gclarity/risev2g/secc/states/WaitForPowerDeliveryReq.java @@ -114,6 +114,9 @@ public ReactionToIncomingMessage processIncomingMessage(Object message) { 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()); + if (powerDeliveryReq.getChargeProgress().equals(ChargeProgressType.RENEGOTIATE) && !getCommSessionContext().isChargeProgressStarted()) { getLogger().error("EVCC wants to renegotiate, but charge progress has not started yet (no " @@ -171,7 +174,8 @@ public boolean isResponseCodeOK(PowerDeliveryReqType powerDeliveryReq) { protected void setEVSEStatus(PowerDeliveryResType powerDeliveryRes) { - if (getCommSessionContext().getRequestedEnergyTransferMode().toString().startsWith("AC")) { + // In case the SECC received a PowerDeliveryReq before a PaymentServiceSelectionReq, the field requestedEnergyTransferMode will be null. So we need to check for it. + if (getCommSessionContext().getRequestedEnergyTransferMode() != null && getCommSessionContext().getRequestedEnergyTransferMode().toString().startsWith("AC")) { /* * The MiscUtils method getJAXBElement() cannot be used here because of the difference in the * class name (ACEVSEStatus) and the name in the XSD (AC_EVSEStatus) @@ -180,7 +184,7 @@ protected void setEVSEStatus(PowerDeliveryResType powerDeliveryRes) { ACEVSEStatusType.class, getCommSessionContext().getACEvseController().getACEVSEStatus(EVSENotificationType.NONE)); powerDeliveryRes.setEVSEStatus(jaxbEVSEStatus); - } else if (getCommSessionContext().getRequestedEnergyTransferMode().toString().startsWith("DC")) { + } else if (getCommSessionContext().getRequestedEnergyTransferMode() != null && getCommSessionContext().getRequestedEnergyTransferMode().toString().startsWith("DC")) { /* * The MiscUtils method getJAXBElement() cannot be used here because of the difference in the * class name (DCEVSEStatus) and the name in the XSD (DC_EVSEStatus) diff --git a/RISE-V2G-Shared/src/main/java/com/v2gclarity/risev2g/shared/enumerations/GlobalValues.java b/RISE-V2G-Shared/src/main/java/com/v2gclarity/risev2g/shared/enumerations/GlobalValues.java index 6e781629..962db322 100644 --- a/RISE-V2G-Shared/src/main/java/com/v2gclarity/risev2g/shared/enumerations/GlobalValues.java +++ b/RISE-V2G-Shared/src/main/java/com/v2gclarity/risev2g/shared/enumerations/GlobalValues.java @@ -71,7 +71,7 @@ public enum GlobalValues { */ V2GTP_HEADER_MAX_PAYLOAD_LENGTH((long) Integer.MAX_VALUE * 2, GlobalTypes.PAYLOAD_LENGTH), - // Protocol versions (1 = IS compliant), see Table 9 + // Protocol version of V2GTP messages (1 = IS compliant), see Table 9 V2GTP_VERSION_1_IS(ByteUtils.toByteFromHexString("01"), GlobalTypes.PROTOCOL_VERSION), // Schema information diff --git a/RISE-V2G-Shared/src/main/java/com/v2gclarity/risev2g/shared/misc/TimeRestrictions.java b/RISE-V2G-Shared/src/main/java/com/v2gclarity/risev2g/shared/misc/TimeRestrictions.java index d9c0db2e..ceda9a7f 100644 --- a/RISE-V2G-Shared/src/main/java/com/v2gclarity/risev2g/shared/misc/TimeRestrictions.java +++ b/RISE-V2G-Shared/src/main/java/com/v2gclarity/risev2g/shared/misc/TimeRestrictions.java @@ -27,12 +27,6 @@ import org.apache.logging.log4j.Logger; import com.v2gclarity.risev2g.shared.enumerations.V2GMessages; -/** - * All time restrictions are given as millisecond values. - * - * @author Marc - * - */ public class TimeRestrictions { private static Logger logger = LogManager.getLogger(TimeRestrictions.class.getSimpleName()); @@ -59,15 +53,6 @@ public class TimeRestrictions { */ public static final int V2G_EVCC_COMMUNICATION_SETUP_TIMEOUT = 20000; - /** - * Timeout for retrieving a response from the ProtoTCPClient after having sent a request - */ - public static final int PROTO_TCP_CLIENT_RESPONSE_TIMEOUT = 30000; - - /** - * Threshold time in seconds for sending the EV controller to sleep - */ - public static final int STAY_AWAKE_THRESHOLD = 125; public static int getV2gEvccMsgTimeout(V2GMessages messageType) { switch(messageType) { diff --git a/RISE-V2G-Shared/src/main/java/com/v2gclarity/risev2g/shared/utils/MiscUtils.java b/RISE-V2G-Shared/src/main/java/com/v2gclarity/risev2g/shared/utils/MiscUtils.java index af142759..59284f4a 100644 --- a/RISE-V2G-Shared/src/main/java/com/v2gclarity/risev2g/shared/utils/MiscUtils.java +++ b/RISE-V2G-Shared/src/main/java/com/v2gclarity/risev2g/shared/utils/MiscUtils.java @@ -95,6 +95,16 @@ public static Inet6Address getLinkLocalAddress() { } + /** + * Is used by the UDP client as well as by the TCP/TLS server whose ports may be in the range + * of 49152 and 65535. + * @return A port number given as an integer value. + */ + public static int getRandomPortNumber() { + return (int) Math.round(Math.random() * (65535-49152)) + 49152; + } + + public static byte[] getMacAddress() { String networkInterfaceConfig = getPropertyValue("network.interface").toString(); NetworkInterface nif = null; @@ -114,14 +124,6 @@ public static byte[] getMacAddress() { return macAddress; } - /** - * Is used by the UDP client as well as by the TCP/TLS server whose ports may be in the range - * of 49152 and 65535. - * @return A port number given as an integer value. - */ - public static int getRandomPortNumber() { - return (int) Math.round(Math.random() * (65535-49152)) + 49152; - } /** * This is a more sophisticated method compared to the getProperty(String propertyName) method @@ -246,6 +248,16 @@ public static Object getPropertyValue(String propertyName) { if (propertyValue.equals("open_exi")) returnValue = "open_exi"; else returnValue = "exificient"; break; + case "voltage.accuracy": // EV property + try { + returnValue = Integer.parseInt(propertyValue); + } catch (NumberFormatException e) { + getLogger().warn("Voltage accuracy '" + propertyValue + "' not supported. " + + "Setting default value to 5.", e); + getV2gEntityConfig().setProperty("voltage.accuracy", "5"); + returnValue = 5; + } + break; default: getLogger().error("No property with name '" + propertyName + "' found"); }