add files

This commit is contained in:
ChiKyun Kim
2025-09-09 13:59:12 +09:00
parent e94b06888d
commit 747aabe224
791 changed files with 3888723 additions and 0 deletions

View File

@@ -0,0 +1 @@
/target

View File

@@ -0,0 +1,144 @@
###############################################################################
# The MIT License (MIT)
#
# Copyright (c) 2015 - 2019 Dr. Marc M<>ltin (V2G Clarity)
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
###############################################################################
# ===========================================================
# Configuration properties for a unique electric vehicle (EV)
# ===========================================================
# Network interface
#------------------
#
# The network interface name like en3 or eth1 of the network interface on which to communicate with the SECC via a
# link-local IPv6 address
network.interface = en0
# Security
#---------
#
# Possible values:
# - true
# - false
# If this value is set to 'false', TCP will be used on transport layer
# If no correct value is provided here, 'false' will be chosen
tls = false
# Contract certificate update time span
#-------------------------------------
#
# Integer value defining the time span in days which precedes the expiration of a contract certificate
# and during which an update of the contract certificate needs to be performed
contract.certificate.update.timespan = 14
# SessionID
#----------
#
# 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
#------------------------
#
# This (single!) value needs to be provided from a previous charging session if charging has been paused.
# Possible values are:
# - Contract
# - ExternalPayment
authentication.mode =
# Requested energy transfer mode
#--------------------------------
#
# This (single!) value needs to be provided from a previous charging session if charging has been paused.
# Possible values are:
# - AC_single_phase_core
# - AC_three_phase_core
# - DC_core
# - DC_extended
# - DC_combo_core
# - DC_unique
energy.transfermode.requested = AC_three_phase_core
#
# Implementation classes
#---------------------------------------------
# If you want to replace the implementation then set the following value
# to the name of your class
# When omitted default dummy implementation will be used
implementation.evcc.controller = com.v2gclarity.risev2g.evcc.evController.DummyEVController
# XML representation of messages
#-------------------------------
#
# Possible values:
# - true
# - false
# If this value is set to 'true', the EXICodec will print each message's XML representation (for debugging purposes)
# If no correct value is provided here, 'false' will be chosen
exi.messages.showxml = true
# Hexadecimal and Base64 representation of messages
#--------------------------------------------------
#
# Possible values:
# - true
# - false
# If this value is set to 'true', the EXICodec will print each message's hexadecimal and Base64 representation (for debugging purposes)
# If no correct value is provided here, 'false' will be chosen
exi.messages.showhex = true
# Extended logging of signature verification
#-------------------------------------------
#
# Possible values:
# - true
# - false
# If this value is set to 'true', extended logging will be printed upon verification of signatures (for debugging purposes)
# If no correct value is provided here, 'false' will be chosen
signature.verification.showlog = true
# EXI codec
#--------------------------------
#
# This (single!) value tells the program which EXI codec to use to en-/decode EXI messages
# Possible values are:
# - exificient
# - 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

View File

@@ -0,0 +1,23 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015-2017 V2G Clarity (Dr. Marc Mültin)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/

View File

@@ -0,0 +1,95 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<groupId>com.v2gclarity.risev2g</groupId>
<artifactId>rise-v2g-parent</artifactId>
<version>1.2.6</version>
<relativePath>../RISE-V2G-PARENT</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>rise-v2g-evcc</artifactId>
<name>rise-v2g-evcc</name>
<dependencies>
<dependency>
<groupId>com.v2gclarity.risev2g</groupId>
<artifactId>rise-v2g-shared</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.version>1.2.5</project.version>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId> <!-- See http://maven.apache.org/plugins/maven-assembly-plugin/usage.html -->
<version>3.1.0</version>
<configuration>
<finalName>rise-v2g-evcc-${project.version}</finalName>
<appendAssemblyId>false</appendAssemblyId>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<mainClass>com.v2gclarity.risev2g.evcc.main.StartEVCC</mainClass>
</manifest>
</archive>
<descriptors> <!-- Same as jar-with-dependencies descriptorRef, but can be adapted if needed -->
<descriptor>src/assembly/bin.xml</descriptor>
</descriptors>
</configuration>
<executions>
<execution>
<id>one-jar-only</id>
<phase>package</phase> <!-- bind to the packaging phase -->
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin> <!-- For copying the keystores and private key to the target folder next to the JAR file -->
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
<executions>
<execution>
<id>copy-resources</id>
<phase>install</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${basedir}/target</outputDirectory>
<resources>
<resource>
<directory>${basedir}</directory>
<includes>
<include>*.p12</include>
<include>*.jks</include>
<include>*.properties</include>
</includes>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.v2gclarity.risev2g</groupId>
<artifactId>rise-v2g-shared</artifactId>
<version>1.2.4</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>

View File

@@ -0,0 +1,18 @@
<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.0.0 http://maven.apache.org/xsd/assembly-2.0.0.xsd">
<!-- TODO: a jarjar format would be better -->
<id>jar-with-dependencies</id>
<formats>
<format>jar</format>
</formats>
<includeBaseDirectory>false</includeBaseDirectory>
<dependencySets>
<dependencySet>
<outputDirectory>/</outputDirectory>
<useProjectArtifact>true</useProjectArtifact>
<unpack>true</unpack>
<scope>runtime</scope>
</dependencySet>
</dependencySets>
</assembly>

View File

@@ -0,0 +1,362 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.evcc.evController;
import java.util.List;
import javax.xml.bind.JAXBElement;
import javax.xml.namespace.QName;
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;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.DCEVStatusType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.EnergyTransferModeType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PMaxScheduleEntryType;
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.PhysicalValueType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ProfileEntryType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.RelativeTimeIntervalType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.SAScheduleListType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.SAScheduleTupleType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.UnitSymbolType;
public class DummyEVController implements IACEVController, IDCEVController {
private Logger logger = LogManager.getLogger(this.getClass().getSimpleName());
private V2GCommunicationSessionEVCC commSessionContext;
private CPStates cpState;
private int chargingLoopCounter;
public DummyEVController() {
setCPState(CPStates.STATE_B); // should be signaled before ISO/IEC 15118 stack initializes
setChargingLoopCounter((short) 0);
}
@Override
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() {
// 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() {
ACEVChargeParameterType acEVChargeParameter = new ACEVChargeParameterType();
acEVChargeParameter.setDepartureTime((long) 7200); // offset in seconds from sending request
PhysicalValueType eAmount = new PhysicalValueType();
eAmount.setMultiplier(new Byte("0"));
eAmount.setUnit(UnitSymbolType.WH);
eAmount.setValue((short) 5000);
acEVChargeParameter.setEAmount(eAmount);
PhysicalValueType evMaxVoltage = new PhysicalValueType();
evMaxVoltage.setMultiplier(new Byte("0"));
evMaxVoltage.setUnit(UnitSymbolType.V);
evMaxVoltage.setValue((short) 400);
acEVChargeParameter.setEVMaxVoltage(evMaxVoltage);
PhysicalValueType evMaxCurrent = new PhysicalValueType();
evMaxCurrent.setMultiplier(new Byte("0"));
evMaxCurrent.setUnit(UnitSymbolType.A);
evMaxCurrent.setValue((short) 32);
acEVChargeParameter.setEVMaxCurrent(evMaxCurrent);
PhysicalValueType evMinCurrent = new PhysicalValueType();
evMinCurrent.setMultiplier(new Byte("0"));
evMinCurrent.setUnit(UnitSymbolType.A);
evMinCurrent.setValue((short) 5);
acEVChargeParameter.setEVMinCurrent(evMinCurrent);
return new JAXBElement<ACEVChargeParameterType>(new QName("urn:iso:15118:2:2013:MsgDataTypes", "AC_EVChargeParameter"),
ACEVChargeParameterType.class,
acEVChargeParameter);
}
@Override
public JAXBElement<DCEVChargeParameterType> getDCEVChargeParamter() {
PhysicalValueType evMaxCurrent = new PhysicalValueType();
evMaxCurrent.setMultiplier(new Byte("0"));
evMaxCurrent.setUnit(UnitSymbolType.A);
evMaxCurrent.setValue((short) 200);
PhysicalValueType evMaxVoltage = new PhysicalValueType();
evMaxVoltage.setMultiplier(new Byte("0"));
evMaxVoltage.setUnit(UnitSymbolType.V);
evMaxVoltage.setValue((short) 400);
PhysicalValueType eAmount = new PhysicalValueType();
eAmount.setMultiplier(new Byte("0"));
eAmount.setUnit(UnitSymbolType.WH);
eAmount.setValue((short) 5000);
DCEVChargeParameterType dcEVChargeParameter = new DCEVChargeParameterType();
dcEVChargeParameter.setDCEVStatus(getDCEVStatus());
dcEVChargeParameter.setEVMaximumCurrentLimit(evMaxCurrent);
dcEVChargeParameter.setEVMaximumVoltageLimit(evMaxVoltage);
dcEVChargeParameter.setEVEnergyRequest(eAmount);
return new JAXBElement<DCEVChargeParameterType>(new QName("urn:iso:15118:2:2013:MsgDataTypes", "DC_EVChargeParameter"),
DCEVChargeParameterType.class,
dcEVChargeParameter);
}
@Override
public ChargingProfileType getChargingProfile() {
ChargingProfileType chargingProfile = new ChargingProfileType();
SAScheduleListType saScheduleList = (SAScheduleListType) getCommSessionContext().getSaSchedules();
// Simply use the first scheduleTuple
SAScheduleTupleType saScheduleTuple = saScheduleList.getSAScheduleTuple().get(0);
// Just follow the PMaxSchedule
PMaxScheduleType pMaxSchedule = (PMaxScheduleType) saScheduleTuple.getPMaxSchedule();
List<PMaxScheduleEntryType> pMaxScheduleEntries = pMaxSchedule.getPMaxScheduleEntry();
// Just copy the provided PMaxSchedule
for (PMaxScheduleEntryType pMaxScheduleEntry : pMaxScheduleEntries) {
ProfileEntryType chargingProfileEntry = new ProfileEntryType();
PhysicalValueType maxPower = new PhysicalValueType();
maxPower.setMultiplier(pMaxScheduleEntry.getPMax().getMultiplier());
maxPower.setUnit(UnitSymbolType.W);
maxPower.setValue(pMaxScheduleEntry.getPMax().getValue());
chargingProfileEntry.setChargingProfileEntryMaxPower(maxPower);
chargingProfileEntry.setChargingProfileEntryMaxNumberOfPhasesInUse(new Byte("3"));
chargingProfileEntry.setChargingProfileEntryStart(
((RelativeTimeIntervalType) pMaxScheduleEntry.getTimeInterval().getValue()).getStart()
);
chargingProfile.getProfileEntry().add(chargingProfileEntry);
}
return chargingProfile;
}
@Override
public short getChosenSAScheduleTupleID() {
return getCommSessionContext().getSaSchedules().getSAScheduleTuple().get(0).getSAScheduleTupleID();
}
public V2GCommunicationSessionEVCC getCommSessionContext() {
return commSessionContext;
}
@Override
public void setCommSessionContext(V2GCommunicationSessionEVCC commSessionContext) {
this.commSessionContext = commSessionContext;
}
@Override
public boolean setCPState(CPStates state) {
getLogger().debug("Changing to state " + state.toString());
this.cpState = state;
return true;
}
@Override
public CPStates getCPState() {
return cpState;
}
@Override
public DCEVStatusType getDCEVStatus() {
DCEVStatusType dcEvStatus = new DCEVStatusType();
dcEvStatus.setEVErrorCode(DCEVErrorCodeType.NO_ERROR);
dcEvStatus.setEVReady(true);
dcEvStatus.setEVRESSSOC(new Byte("50"));
return dcEvStatus;
}
@Override
public PhysicalValueType getTargetVoltage() {
PhysicalValueType targetVoltage = new PhysicalValueType();
targetVoltage.setMultiplier(new Byte("0"));
targetVoltage.setUnit(UnitSymbolType.V);
targetVoltage.setValue((short) 400);
return targetVoltage;
}
@Override
public PhysicalValueType getTargetCurrent() {
PhysicalValueType targetCurrent = new PhysicalValueType();
targetCurrent.setMultiplier(new Byte("0"));
targetCurrent.setUnit(UnitSymbolType.A);
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;
}
@Override
public boolean isBulkChargingComplete() {
return false;
}
@Override
public boolean isChargingComplete() {
return false;
}
@Override
public PhysicalValueType getMaximumVoltageLimit() {
PhysicalValueType maxVoltageLimit = new PhysicalValueType();
maxVoltageLimit.setMultiplier(new Byte("0"));
maxVoltageLimit.setUnit(UnitSymbolType.V);
maxVoltageLimit.setValue((short) 400);
return maxVoltageLimit;
}
@Override
public PhysicalValueType getMaximumCurrentLimit() {
PhysicalValueType maxCurrentLimit = new PhysicalValueType();
maxCurrentLimit.setMultiplier(new Byte("0"));
maxCurrentLimit.setUnit(UnitSymbolType.A);
maxCurrentLimit.setValue((short) 32);
return maxCurrentLimit;
}
@Override
public PhysicalValueType getMaximumPowerLimit() {
PhysicalValueType maxPowerLimit = new PhysicalValueType();
maxPowerLimit.setMultiplier(new Byte("3"));
maxPowerLimit.setUnit(UnitSymbolType.W);
maxPowerLimit.setValue((short) 63);
return maxPowerLimit;
}
@Override
public PhysicalValueType getRemainingTimeToFullSOC() {
PhysicalValueType remainingTimeToFullSOC = new PhysicalValueType();
remainingTimeToFullSOC.setMultiplier(new Byte("0"));
remainingTimeToFullSOC.setUnit(UnitSymbolType.S);
remainingTimeToFullSOC.setValue((short) 1800);
return remainingTimeToFullSOC;
}
@Override
public PhysicalValueType getRemainingTimeToBulkSOC() {
PhysicalValueType remainingTimeToBulkSOC = new PhysicalValueType();
remainingTimeToBulkSOC.setMultiplier(new Byte("0"));
remainingTimeToBulkSOC.setUnit(UnitSymbolType.S);
remainingTimeToBulkSOC.setValue((short) 900);
return remainingTimeToBulkSOC;
}
public Logger getLogger() {
return logger;
}
@Override
public void adjustMaxCurrent(PhysicalValueType evseMaxCurrent) {
short multiplier = (short) (evseMaxCurrent.getMultiplier() & 0xFF);
getLogger().info("Adjusting max current to " + evseMaxCurrent.getValue() * Math.pow(10, multiplier) + " A");
}
@Override
public boolean isChargingLoopActive() {
// Keep charging until 100 charging loops are finished
if (getChargingLoopCounter() < 100) {
setChargingLoopCounter(getChargingLoopCounter() + 1);
/*
* OPTIONAL:
* Trigger a renegotiation after 50 charging loops (for testing purposes); you can comment this out if you do not want to test an EV-triggered renegotiation
*/
if (getChargingLoopCounter() == 50) {
getCommSessionContext().setRenegotiationRequested(true);
getLogger().debug("EV triggered a renegotiation (for testing purposes)");
}
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;
}
public int getChargingLoopCounter() {
return chargingLoopCounter;
}
public void setChargingLoopCounter(int chargingLoopCounter) {
this.chargingLoopCounter = chargingLoopCounter;
}
@Override
public DCEVPowerDeliveryParameterType getEVPowerDeliveryParameter() {
DCEVPowerDeliveryParameterType dcEvPowerDeliveryParameter = new DCEVPowerDeliveryParameterType();
dcEvPowerDeliveryParameter.setBulkChargingComplete(false);
dcEvPowerDeliveryParameter.setChargingComplete(false);
dcEvPowerDeliveryParameter.setDCEVStatus(getDCEVStatus());
return dcEvPowerDeliveryParameter;
}
}

View File

@@ -0,0 +1,45 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.evcc.evController;
import javax.xml.bind.JAXBElement;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ACEVChargeParameterType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PhysicalValueType;
public interface IACEVController extends IEVController {
/**
* Returns the charge parameter for AC charging
* @return The battery specific charge parameter for the current charging session
*/
public JAXBElement<ACEVChargeParameterType> getACEVChargeParamter();
/**
* Indicates the maximum line current per phase the EV can draw (as allowed by the SECC)
* @param evseMaxCurrent
*/
public void adjustMaxCurrent(PhysicalValueType evseMaxCurrent);
}

View File

@@ -0,0 +1,118 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.evcc.evController;
import javax.xml.bind.JAXBElement;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.DCEVChargeParameterType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.DCEVPowerDeliveryParameterType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.DCEVStatusType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PhysicalValueType;
public interface IDCEVController extends IEVController {
/**
* Returns the charge parameter for AC charging
* @return The battery specific charge parameter for the current charging session
*/
public JAXBElement<DCEVChargeParameterType> getDCEVChargeParamter();
/**
* Returns the EV status parameter for DC charging
* @return The EV status for DC charging for the current charging session
*/
public DCEVStatusType getDCEVStatus();
/**
* Returns the target voltage for DC charging
* @return Target voltage given as a PhysicalValueType
*/
public PhysicalValueType getTargetVoltage();
/**
* Returns the target current for DC charging
* @return Target current given as a PhysicalValueType
*/
public PhysicalValueType getTargetCurrent();
/**
* If set to TRUE, the EV indicates that bulk charging (approx. 80% SOC) is complete.
* @return True, if bulk charge is complete, false otherwise
*/
public boolean isBulkChargingComplete();
/**
* If set to TRUE, the EV indicates that charging process is complete.
* @return True, if charging process is complete, false otherwise
*/
public boolean isChargingComplete();
/**
* Returns the maximum voltage limit for DC charging
* @return The maximum voltage given as a PhysicalValueType
*/
public PhysicalValueType getMaximumVoltageLimit();
/**
* Returns the maximum current limit for DC charging
* @return The maximum current given as a PhysicalValueType
*/
public PhysicalValueType getMaximumCurrentLimit();
/**
* Returns the maximum power limit for DC charging
* @return The maximum power given as a PhysicalValueType
*/
public PhysicalValueType getMaximumPowerLimit();
/**
* Returns the estimated or calculated time until full charge (100% SOC) is complete
* @return The estimated time given as a PhysicalValueType
*/
public PhysicalValueType getRemainingTimeToFullSOC();
/**
* Returns the estimated or calculated time until bulk charge (approx. 80% SOC) is complete
* @return The estimated time given as a PhysicalValueType
*/
public PhysicalValueType getRemainingTimeToBulkSOC();
/**
* Returns the DC_EVPowerDeliverParameter
* @return The DC_EVPowerDeliverParameter
*/
public DCEVPowerDeliveryParameterType getEVPowerDeliveryParameter();
}

View File

@@ -0,0 +1,95 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.evcc.evController;
import com.v2gclarity.risev2g.evcc.session.V2GCommunicationSessionEVCC;
import com.v2gclarity.risev2g.shared.enumerations.CPStates;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargingProfileType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.EnergyTransferModeType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PaymentOptionListType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PaymentOptionType;
public interface IEVController {
/**
* Provides a reference to the current communication session for
* this controller instance.
* @param commSessionContext The active communication session
*/
public void setCommSessionContext(V2GCommunicationSessionEVCC commSessionContext);
/**
* Returns the user-chosen payment method, either external identification means (EIM) such as an
* RFID card or via Plug-and-Charge (PnC)
* @return The payment option Contract or ExternalPayment
*/
public PaymentOptionType getPaymentOption();
/**
* Returns the EnergyTransferMode chosen by the driver
* @return The chosen EnergyTransferMode
*/
public EnergyTransferModeType getRequestedEnergyTransferMode();
/**
* Returns the specific charging profile for the current charging session
* (i.e. maximum amount of power drawn over time)
* @return The charging profile with a list of profile entries
*/
public ChargingProfileType getChargingProfile();
/**
* Returns the unique identifier within a charging session for a SAScheduleTuple element
* contained in the list of SASchedules delivered by the EVSE. An SAScheduleTupleID remains a
* unique identifier for one schedule throughout a charging session.
* @return The unique ID given as a short value
*/
public short getChosenSAScheduleTupleID();
/**
* Signals a CP state according to IEC 61851-1 (State A, B, C or D)
* @param state
* @return True, if the state signaling was successful, false otherwise
*/
public boolean setCPState(CPStates state);
/**
* Returns the current CP state according IEC 61851-1 (State A, B, C or D)
* @return The respective CP state
*/
public CPStates getCPState();
/**
* Provides information on whether the charging loop should be active to charge the EV's battery, or not
*
* @return True, if charging process should be continued, false otherwise
*/
public boolean isChargingLoopActive();
}

View File

@@ -0,0 +1,37 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.evcc.main;
import com.v2gclarity.risev2g.evcc.session.V2GCommunicationSessionHandlerEVCC;
import com.v2gclarity.risev2g.shared.enumerations.GlobalValues;
import com.v2gclarity.risev2g.shared.utils.MiscUtils;
public class StartEVCC {
public static void main(String[] args) {
MiscUtils.loadProperties(GlobalValues.EVCC_CONFIG_PROPERTIES_PATH.toString());
new V2GCommunicationSessionHandlerEVCC();
}
}

View File

@@ -0,0 +1,50 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.evcc.misc;
import com.v2gclarity.risev2g.evcc.evController.DummyEVController;
import com.v2gclarity.risev2g.evcc.evController.IEVController;
import com.v2gclarity.risev2g.evcc.session.V2GCommunicationSessionEVCC;
import com.v2gclarity.risev2g.shared.misc.V2GImplementationFactory;
/**
* Implementation factory for the EVCC controller
*
*/
public class EVCCImplementationFactory extends V2GImplementationFactory {
/**
* Creates the controller for the EVCC application
* @param commSessionContext the session the backend will be connected to
* @return
*/
public static IEVController createEVController(V2GCommunicationSessionEVCC commSessionContext) {
IEVController instance = buildFromProperties("implementation.evcc.controller", IEVController.class);
if (instance == null) {
instance = new DummyEVController();
}
instance.setCommSessionContext(commSessionContext);
return instance;
}
}

View File

@@ -0,0 +1,522 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.evcc.session;
import java.util.ArrayList;
import java.util.List;
import java.util.Observable;
import java.util.Observer;
import com.v2gclarity.risev2g.evcc.evController.IEVController;
import com.v2gclarity.risev2g.evcc.misc.EVCCImplementationFactory;
import com.v2gclarity.risev2g.evcc.states.WaitForAuthorizationRes;
import com.v2gclarity.risev2g.evcc.states.WaitForCableCheckRes;
import com.v2gclarity.risev2g.evcc.states.WaitForCertificateInstallationRes;
import com.v2gclarity.risev2g.evcc.states.WaitForCertificateUpdateRes;
import com.v2gclarity.risev2g.evcc.states.WaitForChargeParameterDiscoveryRes;
import com.v2gclarity.risev2g.evcc.states.WaitForChargingStatusRes;
import com.v2gclarity.risev2g.evcc.states.WaitForCurrentDemandRes;
import com.v2gclarity.risev2g.evcc.states.WaitForMeteringReceiptRes;
import com.v2gclarity.risev2g.evcc.states.WaitForPaymentDetailsRes;
import com.v2gclarity.risev2g.evcc.states.WaitForPaymentServiceSelectionRes;
import com.v2gclarity.risev2g.evcc.states.WaitForPowerDeliveryRes;
import com.v2gclarity.risev2g.evcc.states.WaitForPreChargeRes;
import com.v2gclarity.risev2g.evcc.states.WaitForServiceDetailRes;
import com.v2gclarity.risev2g.evcc.states.WaitForServiceDiscoveryRes;
import com.v2gclarity.risev2g.evcc.states.WaitForSessionSetupRes;
import com.v2gclarity.risev2g.evcc.states.WaitForSessionStopRes;
import com.v2gclarity.risev2g.evcc.states.WaitForSupportedAppProtocolRes;
import com.v2gclarity.risev2g.evcc.states.WaitForWeldingDetectionRes;
import com.v2gclarity.risev2g.evcc.transportLayer.StatefulTransportLayerClient;
import com.v2gclarity.risev2g.evcc.transportLayer.TCPClient;
import com.v2gclarity.risev2g.evcc.transportLayer.TLSClient;
import com.v2gclarity.risev2g.shared.enumerations.CPStates;
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.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;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.SelectedServiceListType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.SelectedServiceType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ServiceListType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.V2GMessage;
// The state machine
public class V2GCommunicationSessionEVCC extends V2GCommunicationSession implements Observer {
private long v2gEVCCCommunicationSetupTimer;
private String evseID;
private PaymentOptionType selectedPaymentOption;
private ReactionToIncomingMessage reactionToIncomingMessage;
/*
* chargeParameterDiscoveryReq is saved in the session because it might need to be resent in case
* the EVSEProcessing parameter of the respective response message is set to "Ongoing"
* (saves some processing time)
*/
private ChargeParameterDiscoveryReqType chargeParameterDiscoveryReq;
private ChargingSessionType chargingSession;
private boolean renegotiationRequested;
private ChargingProfileType chargingProfile;
private ServiceListType offeredServices;
private SelectedServiceListType selectedServices;
private ArrayList<Integer> serviceDetailsToBeRequested;
private EnergyTransferModeType requestedEnergyTransferMode;
private long evseScheduleReceived; // The timestamp of receiving the SAScheduleList from the EVSE, is used as a reference
private List<AppProtocolType> supportedAppProtocols;
private AppProtocolType chosenAppProtocol;
private boolean oldSessionJoined;
private IEVController evController;
private long evseTimeStamp;
private SAScheduleListType saSchedules;
private long saSchedulesReceived;
private CPStates changeToState; // signals a needed state change (checked when sending the request message)
private StatefulTransportLayerClient transportLayerClient;
private ContractCertificateStatus contractCertStatus;
private long ongoingTimer; // used for V2G_EVCC_Ongoing_Timer, V2G_EVCC_PreCharge_Timer, V2G_EVCC_CableCheck_Timer
private boolean isOngoingTimerActive;
private byte[] sentGenChallenge;
public V2GCommunicationSessionEVCC(StatefulTransportLayerClient transportLayerClient) {
setTransportLayerClient(transportLayerClient);
getStates().put(V2GMessages.SUPPORTED_APP_PROTOCOL_RES, new WaitForSupportedAppProtocolRes(this));
getStates().put(V2GMessages.SESSION_SETUP_RES, new WaitForSessionSetupRes(this));
getStates().put(V2GMessages.SERVICE_DISCOVERY_RES, new WaitForServiceDiscoveryRes(this));
getStates().put(V2GMessages.SERVICE_DETAIL_RES, new WaitForServiceDetailRes(this));
getStates().put(V2GMessages.PAYMENT_SERVICE_SELECTION_RES, new WaitForPaymentServiceSelectionRes(this));
getStates().put(V2GMessages.CERTIFICATE_INSTALLATION_RES, new WaitForCertificateInstallationRes(this));
getStates().put(V2GMessages.CERTIFICATE_UPDATE_RES, new WaitForCertificateUpdateRes(this));
getStates().put(V2GMessages.PAYMENT_DETAILS_RES, new WaitForPaymentDetailsRes(this));
getStates().put(V2GMessages.AUTHORIZATION_RES, new WaitForAuthorizationRes(this));
getStates().put(V2GMessages.CHARGE_PARAMETER_DISCOVERY_RES, new WaitForChargeParameterDiscoveryRes(this));
getStates().put(V2GMessages.CABLE_CHECK_RES, new WaitForCableCheckRes(this));
getStates().put(V2GMessages.PRE_CHARGE_RES, new WaitForPreChargeRes(this));
getStates().put(V2GMessages.POWER_DELIVERY_RES, new WaitForPowerDeliveryRes(this));
getStates().put(V2GMessages.CHARGING_STATUS_RES, new WaitForChargingStatusRes(this));
getStates().put(V2GMessages.CURRENT_DEMAND_RES, new WaitForCurrentDemandRes(this));
getStates().put(V2GMessages.METERING_RECEIPT_RES, new WaitForMeteringReceiptRes(this));
getStates().put(V2GMessages.WELDING_DETECTION_RES, new WaitForWeldingDetectionRes(this));
getStates().put(V2GMessages.SESSION_STOP_RES, new WaitForSessionStopRes(this));
setStartState(getStates().get(V2GMessages.SUPPORTED_APP_PROTOCOL_RES));
setCurrentState(getStartState());
// configure which EV controller implementation to use
// TODO the EV controller needs to run as a separate Thread (to receive notifications from the EV and to avoid blocking calls to the controller)
setEvController(EVCCImplementationFactory.createEVController(this));
/*
* Is needed for measuring the time span between transition to state B (plug-in) and receipt
* of a SessionSetupRes (see V2G_EVCC_COMMUNICATION_SETUP_TIMEOUT in TimeRestrictions.java)
* TODO check if this timing requirement is still up to date
*/
setV2gEVCCCommunicationSetupTimer(System.nanoTime());
// 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" +
"\n*******************************************");
}
@Override
public void update(Observable obs, Object obj) {
if ((obs instanceof TCPClient || obs instanceof TLSClient) && obj instanceof byte[]) {
setV2gTpMessage(new V2GTPMessage((byte[]) obj));
if (getMessageHandler().isV2GTPMessageValid(getV2gTpMessage())) {
/*
* We need to decide which schema to use for decoding the EXI encoded message. Only
* the supportedAppProtocolReq/Res message uses a different schema
*/
if (getCurrentState().equals(getStates().get(V2GMessages.SUPPORTED_APP_PROTOCOL_RES))) {
obj = (SupportedAppProtocolRes) getMessageHandler().exiToSuppAppProtocolMsg(getV2gTpMessage().getPayload());
} else {
try {
obj = (V2GMessage) getMessageHandler().exiToV2gMsg(getV2gTpMessage().getPayload());
} catch (ClassCastException e) {
terminateSession("Received incoming message is not a valid V2GTPMessage\n" + e, false);
}
}
processReaction(getCurrentState().processIncomingMessage(obj));
} else {
terminateSession("Received incoming message is not a valid V2GTPMessage", false);
}
} else if ((obs instanceof TCPClient || obs instanceof TLSClient) && obj == null) {
terminateSession("Transport layer has notified an error", false);
} else {
terminateSession("Notification received, but sending entity or received object not identifiable", false);
}
}
private void processReaction(ReactionToIncomingMessage reactionToIncomingMessage) {
if (reactionToIncomingMessage instanceof SendMessage) {
send((SendMessage) reactionToIncomingMessage);
} else if (reactionToIncomingMessage instanceof TerminateSession) {
deleteSessionProperties();
terminateSession((TerminateSession) reactionToIncomingMessage);
} else if (reactionToIncomingMessage instanceof PauseSession) {
saveSessionProperties();
pauseSession((PauseSession) reactionToIncomingMessage);
} else if (reactionToIncomingMessage instanceof ChangeProcessingState) {
setCurrentState(((ChangeProcessingState) reactionToIncomingMessage).getNewState());
processReaction(
getCurrentState().processIncomingMessage(reactionToIncomingMessage)
); // TODO ist das korrekt?! bspw. wenn Renegotiation angefragt wird von EVSE?
} else {
terminateSession("Reaction to incoming message is undefined", false);
}
}
public void send(SendMessage sendingParams) {
// Only EXI encoded messages starting from SessionSetupReq will be sent here
setV2gTpMessage(new V2GTPMessage(GlobalValues.V2GTP_VERSION_1_IS.getByteValue(),
GlobalValues.V2GTP_PAYLOAD_TYPE_EXI_ENCODED_V2G_MESSAGE.getByteArrayValue(),
(byte[]) getMessageHandler().v2gMsgToExi(sendingParams.getPayload()))
);
getTransportLayerClient().send(getV2gTpMessage(), sendingParams.getTimeout());
// Check for necessary CP state change (see [V2G2-847])
if (getChangeToState() != null) {
if (getEvController().setCPState(getChangeToState())) setChangeToState(null);
else terminateSession("State change to " + getChangeToState().toString() + " not successful", false);
}
if (sendingParams.getNextState() != null) {
setCurrentState(sendingParams.getNextState());
} else {
terminateSession("State machine interrupted, no new state provided", true);
}
}
private void saveSessionProperties() {
// 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() {
// 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());
}
private void setV2gEVCCCommunicationSetupTimer(
long v2gEVCCCommunicationSetupTimer) {
this.v2gEVCCCommunicationSetupTimer = v2gEVCCCommunicationSetupTimer;
}
public long getV2gEVCCCommunicationSetupTimer() {
return v2gEVCCCommunicationSetupTimer;
}
public String getEvseID() {
return evseID;
}
public void setEvseID(String evseID) {
this.evseID = evseID;
}
public PaymentOptionType getSelectedPaymentOption() {
return selectedPaymentOption;
}
public void setSelectedPaymentOption(PaymentOptionType selectedPaymentOption) {
this.selectedPaymentOption = selectedPaymentOption;
}
public ChargeParameterDiscoveryReqType getChargeParameterDiscoveryReq() {
return chargeParameterDiscoveryReq;
}
public void setChargeParameterDiscoveryReq(
ChargeParameterDiscoveryReqType chargeParameterDiscoveryReq) {
this.chargeParameterDiscoveryReq = chargeParameterDiscoveryReq;
}
public ReactionToIncomingMessage getReactionToIncomingMessage() {
return reactionToIncomingMessage;
}
public void setReactionToIncomingMessage(ReactionToIncomingMessage reactionToIncomingMessage) {
this.reactionToIncomingMessage = reactionToIncomingMessage;
}
public boolean isRenegotiationRequested() {
return renegotiationRequested;
}
public void setRenegotiationRequested(boolean renegotiationRequested) {
this.renegotiationRequested = renegotiationRequested;
}
public long getEvseScheduleReceived() {
return evseScheduleReceived;
}
public void setEvseScheduleReceived(long evseScheduleReceived) {
this.evseScheduleReceived = evseScheduleReceived;
}
public ChargingProfileType getChargingProfile() {
return chargingProfile;
}
public void setChargingProfile(ChargingProfileType chargingProfile) {
this.chargingProfile = chargingProfile;
}
public List<AppProtocolType> getSupportedAppProtocols() {
return supportedAppProtocols;
}
public void setSupportedAppProtocols(List<AppProtocolType> supportedAppProtocols) {
this.supportedAppProtocols = supportedAppProtocols;
}
public AppProtocolType getChosenAppProtocol() {
return chosenAppProtocol;
}
public void setChosenAppProtocol(AppProtocolType chosenAppProtocol) {
this.chosenAppProtocol = chosenAppProtocol;
}
public boolean isOldSessionJoined() {
return oldSessionJoined;
}
public void setOldSessionJoined(boolean oldSessionJoined) {
this.oldSessionJoined = oldSessionJoined;
}
public IEVController getEvController() {
return evController;
}
public void setEvController(IEVController evController) {
this.evController = evController;
}
public long getEvseTimeStamp() {
return evseTimeStamp;
}
public void setEvseTimeStamp(long evseTimeStamp) {
this.evseTimeStamp = evseTimeStamp;
}
public EnergyTransferModeType getRequestedEnergyTransferMode() {
return requestedEnergyTransferMode;
}
public void setRequestedEnergyTransferMode(
EnergyTransferModeType requestedEnergyTransferMode) {
this.requestedEnergyTransferMode = requestedEnergyTransferMode;
}
public SAScheduleListType getSaSchedules() {
return saSchedules;
}
public void setSaSchedules(SAScheduleListType saSchedules) {
this.saSchedules = saSchedules;
this.saSchedulesReceived = System.nanoTime();
}
public long getSaSchedulesReceived() {
return saSchedulesReceived;
}
public CPStates getChangeToState() {
return changeToState;
}
public void setChangeToState(CPStates changeToState) {
this.changeToState = changeToState;
}
public StatefulTransportLayerClient getTransportLayerClient() {
return transportLayerClient;
}
public void setTransportLayerClient(StatefulTransportLayerClient transportLayerClient) {
this.transportLayerClient = transportLayerClient;
}
public SelectedServiceListType getSelectedServices() {
if (selectedServices == null) setSelectedServices(new SelectedServiceListType());
return selectedServices;
}
public void setSelectedServices(SelectedServiceListType selectedServices) {
this.selectedServices = selectedServices;
}
public ServiceListType getOfferedServices() {
if (offeredServices == null) setOfferedServices(new ServiceListType());
return offeredServices;
}
public void setOfferedServices(ServiceListType offeredServices) {
this.offeredServices = offeredServices;
}
public ArrayList<Integer> getServiceDetailsToBeRequested() {
if (serviceDetailsToBeRequested == null) {
serviceDetailsToBeRequested = new ArrayList<Integer>();
}
return serviceDetailsToBeRequested;
}
public void setServiceDetailsToBeRequested(
ArrayList<Integer> serviceDetailsToBeRequested) {
this.serviceDetailsToBeRequested = serviceDetailsToBeRequested;
}
/**
* Checks if the respective service for installing or updating a certificate is offered by the SECC and
* has been selected by the EVCC.
*
* @param parameterSetID 1 for installing a certificate, 2 for updating a certificate
* @return True, if the respective certificate service is available, false otherwise
*/
public boolean isCertificateServiceAvailable(short parameterSetID) {
for (SelectedServiceType service : getSelectedServices().getSelectedService()) {
if (service.getServiceID() == 2 && // ServiceID 2 refers to the 'Certificate' service
service.getParameterSetID() != null &&
service.getParameterSetID() == parameterSetID)
return true;
}
return false;
}
public ContractCertificateStatus getContractCertStatus() {
return contractCertStatus;
}
public void setContractCertStatus(ContractCertificateStatus contractCertStatus) {
this.contractCertStatus = contractCertStatus;
}
public long getOngoingTimer() {
return ongoingTimer;
}
public void setOngoingTimer(long ongoingTimer) {
this.ongoingTimer = ongoingTimer;
}
public boolean isOngoingTimerActive() {
return isOngoingTimerActive;
}
public void setOngoingTimerActive(boolean isOngoingTimerActive) {
this.isOngoingTimerActive = isOngoingTimerActive;
}
public byte[] getSentGenChallenge() {
return sentGenChallenge;
}
public void setSentGenChallenge(byte[] sentGenChallenge) {
this.sentGenChallenge = sentGenChallenge;
}
public ChargingSessionType getChargingSession() {
return chargingSession;
}
public void setChargingSession(ChargingSessionType chargingSession) {
this.chargingSession = chargingSession;
}
}

View File

@@ -0,0 +1,409 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.evcc.session;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import java.util.Observable;
import java.util.Observer;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.v2gclarity.risev2g.evcc.transportLayer.StatefulTransportLayerClient;
import com.v2gclarity.risev2g.evcc.transportLayer.TCPClient;
import com.v2gclarity.risev2g.evcc.transportLayer.TLSClient;
import com.v2gclarity.risev2g.evcc.transportLayer.UDPClient;
import com.v2gclarity.risev2g.shared.enumerations.GlobalValues;
import com.v2gclarity.risev2g.shared.enumerations.V2GMessages;
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.TimeRestrictions;
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.SECCDiscoveryReq;
import com.v2gclarity.risev2g.shared.v2gMessages.SECCDiscoveryRes;
import com.v2gclarity.risev2g.shared.v2gMessages.appProtocol.AppProtocolType;
import com.v2gclarity.risev2g.shared.v2gMessages.appProtocol.SupportedAppProtocolReq;
public class V2GCommunicationSessionHandlerEVCC implements Observer {
private Logger logger = LogManager.getLogger(this.getClass().getSimpleName());
private MessageHandler messageHandler;
private int seccDiscoveryRequestCounter;
private int sessionRetryCounter;
private byte security;
private V2GCommunicationSessionEVCC v2gCommunicationSessionEVCC;
private V2GTPMessage v2gTpMessage;
private Thread transportLayerThread;
private StatefulTransportLayerClient transportLayerClient;
public V2GCommunicationSessionHandlerEVCC() {
setMessageHandler(MessageHandler.getInstance());
setSecurity(
(MiscUtils.getPropertyValue("tls") != null ?
(byte) MiscUtils.getPropertyValue("tls") :
GlobalValues.V2G_SECURITY_WITHOUT_TLS.getByteValue())
);
setSessionRetryCounter(0);
initialize();
}
private void initialize() {
UDPClient udpClient = UDPClient.getInstance();
if (!udpClient.initialize()) {
logger.fatal("UDP client cannot be initialized, ISO 15118 communication cannot continue");
return;
};
byte[] udpResponse = null;
SECCDiscoveryRes seccDiscoveryRes = null;
setSessionRetryCounter(getSeccDiscoveryRequestCounter() + 1);
// Create SECCDiscoveryReq and check response
while (getSeccDiscoveryRequestCounter() < TimeRestrictions.SDP_REQUEST_MAX_COUNTER) {
udpResponse = sendSECCDiscoveryReq(getSecurity());
if (udpResponse == null) {
getLogger().warn("Number of SECCDiscoveryReq messages so far: " + getSeccDiscoveryRequestCounter());
} else {
setV2gTpMessage(new V2GTPMessage(udpResponse));
if (getMessageHandler().isV2GTPMessageValid(getV2gTpMessage())) {
seccDiscoveryRes = new SECCDiscoveryRes(getV2gTpMessage().getPayload());
break; // if everything is OK and a valid SDP response is received, the while loop is stopped here
} else {
continue;
}
}
}
// SECCDiscoveryRes was successful, establish a new V2GCommunicationSessionEVCC and initiate the respective TCP client connection
startNewSession(seccDiscoveryRes);
}
private boolean startNewSession(SECCDiscoveryRes seccDiscoveryRes) {
/*
* Establish a new V2GCommunicationSessionEVCC if SECCDiscoveryRes was successful and initiate
* the respective TCP client connection
*/
if (seccDiscoveryRes != null) {
// Reset SECCDiscoveryReq retry counter
setSeccDiscoveryRequestCounter(0);
Inet6Address seccAddress;
try {
// TODO seems to work, but is the needed scope ID really the one of the UDP client?
seccAddress = Inet6Address.getByAddress(
InetAddress.getByAddress(seccDiscoveryRes.getSeccIPAddress()).getHostAddress(),
seccDiscoveryRes.getSeccIPAddress(),
UDPClient.getInstance().getUdpClientAddress().getScopeId()
);
} catch (UnknownHostException e) {
getLogger().fatal("SECC address could not be resolved", e);
return false;
}
getLogger().info("UDP server responded: SECC reachable at address " +
seccAddress.getHostAddress() + " and port " +
ByteUtils.toIntFromByteArray(seccDiscoveryRes.getSeccPort()));
if (!startTransportLayerClient(seccDiscoveryRes, seccAddress)) return false;
setV2gCommunicationSessionEVCC(new V2GCommunicationSessionEVCC(getTransportLayerClient()));
/*
* Tell the TCP- or TLSClient to notify if
* - a new V2GTPMessage has arrived
* - a timeout has occurred while waiting for the respective response message
*/
getTransportLayerClient().addObserver(getV2gCommunicationSessionEVCC());
getV2gCommunicationSessionEVCC().addObserver(this);
// Set TLS security flag for communication session
boolean secureConn = (((Byte) getSecurity()).compareTo((Byte) GlobalValues.V2G_SECURITY_WITH_TLS.getByteValue()) == 0) ? true : false;
getV2gCommunicationSessionEVCC().setTlsConnection(secureConn);
sendSupportedAppProtocolReq();
} else {
getLogger().fatal("Maximum number of SECCDiscoveryReq messages reached");
return false;
}
return true;
}
private boolean startTransportLayerClient(SECCDiscoveryRes seccDiscoveryRes, Inet6Address seccAddress) {
boolean securityAgreement = Byte.compare(seccDiscoveryRes.getSecurity(), getSecurity()) == 0 ? true : false;
/*
* Note 8 of ISO/IEC 15118-2 states:
* "Not supporting TLS in the SECC might lead in general to aborted charging sessions
* with particular EVs as it is in the responsibility of the EV to accept sessions
* without TLS"
*
* This implementation of an EVCC will only accept TLS connections to the SECC if requested on
* EVCC-side. However, this is the place to change the implementation if wanted. It is however
* strongly recommended to always choose TLS.
*/
if (securityAgreement && isSecureCommunication()) {
if (TLSClient.getInstance().initialize(
seccAddress,
ByteUtils.toIntFromByteArray(seccDiscoveryRes.getSeccPort()))) {
setTransportLayerClient(TLSClient.getInstance());
} else {
getLogger().fatal("TLS client could not be initialized");
return false;
}
} else if (securityAgreement && !isSecureCommunication()) {
if (TCPClient.getInstance().initialize(
seccAddress,
ByteUtils.toIntFromByteArray(seccDiscoveryRes.getSeccPort()))) {
setTransportLayerClient(TCPClient.getInstance());
} else {
getLogger().fatal("TCP client could not be initialized");
return false;
}
} else {
getLogger().fatal("EVCC and SECC could not agree on security level of transport layer");
return false;
}
setTransportLayerThread(new Thread(getTransportLayerClient()));
getTransportLayerThread().start();
return true;
}
@Override
public void update(Observable obs, Object obj) {
if (obs instanceof V2GCommunicationSessionEVCC &&
(obj instanceof PauseSession || obj instanceof TerminateSession)) {
// In case of pausing or terminating a session the transport layer client must be stopped
getTransportLayerClient().stop();
getTransportLayerThread().interrupt();
if (obj instanceof PauseSession) {
/*
* If some action is needed by the sessionHandler when pausing, it can be done here.
* If TCP/TLS client sends notification, it should always be a TerminateSession instance
* (because a failure of the connection to the TCP/TLS server is its only reason for
* notification).
*/
} else if (obj instanceof TerminateSession) {
terminate((TerminateSession) obj);
}
} else if (obs instanceof TCPClient || obs instanceof TLSClient) {
// TCP- and TLSClient already stop themselves and interrupt their threads before notifying
terminate((TerminateSession) obj);
} else {
getLogger().warn("Notification coming from " + obs.getClass().getSimpleName() +
" unknown: " + obj.getClass().getSimpleName());
}
}
private void terminate(TerminateSession terminationObject) {
getTransportLayerThread().interrupt();
setV2gCommunicationSessionEVCC(null);
UDPClient.getInstance().stop();;
if (!terminationObject.isSuccessfulTermination()) {
// TODO should there be a retry of the communication session, and if yes, how often?
}
}
private byte[] sendSECCDiscoveryReq(byte security) {
/*
* The standard in principle allows to set UDP as requested transport protocol, however,
* there is no good reason for actually not using TCP (or TLS). Therefore this is not a
* configurable option.
*/
SECCDiscoveryReq seccDiscoveryReq =
new SECCDiscoveryReq(security, GlobalValues.V2G_TRANSPORT_PROTOCOL_TCP.getByteValue());
setV2gTpMessage(
new V2GTPMessage(GlobalValues.V2GTP_VERSION_1_IS.getByteValue(),
GlobalValues.V2GTP_PAYLOAD_TYPE_SDP_REQUEST_MESSAGE.getByteArrayValue(),
seccDiscoveryReq.getPayload())
);
getLogger().debug("Preparing to send SECCDiscoveryReq ...");
setSeccDiscoveryRequestCounter(getSeccDiscoveryRequestCounter() + 1);
return UDPClient.getInstance().send(getV2gTpMessage());
}
/**
* All supported versions of the ISO/IEC 15118-2 protocol are listed here.
* Currently, only IS version of April 2014 is supported (see [V2G2-098]), more could be provided here.
*
* @return A list of supported of AppProtocol entries
*/
private void sendSupportedAppProtocolReq() {
List<AppProtocolType> supportedAppProtocols = new ArrayList<AppProtocolType>();
AppProtocolType appProtocol1 = new AppProtocolType();
appProtocol1.setProtocolNamespace(GlobalValues.V2G_CI_MSG_DEF_NAMESPACE.toString());
appProtocol1.setVersionNumberMajor(2);
appProtocol1.setVersionNumberMinor(0);
appProtocol1.setSchemaID((short) 10);
appProtocol1.setPriority((short) 1);
supportedAppProtocols.add(appProtocol1);
SupportedAppProtocolReq supportedAppProtocolReq = new SupportedAppProtocolReq();
supportedAppProtocolReq.getAppProtocol().add(appProtocol1);
// Save the list of supported protocols
getV2gCommunicationSessionEVCC().setSupportedAppProtocols(supportedAppProtocols);
setV2gTpMessage(
new V2GTPMessage(
GlobalValues.V2GTP_VERSION_1_IS.getByteValue(),
GlobalValues.V2GTP_PAYLOAD_TYPE_EXI_ENCODED_V2G_MESSAGE.getByteArrayValue(),
(byte[]) getMessageHandler().suppAppProtocolMsgToExi(supportedAppProtocolReq)
)
);
getLogger().debug("Preparing to send SupportedAppProtocolReq ...");
if (isSecureCommunication()) {
TLSClient.getInstance().send(
getV2gTpMessage(),
TimeRestrictions.getV2gEvccMsgTimeout(V2GMessages.SUPPORTED_APP_PROTOCOL_RES));
} else {
TCPClient.getInstance().send(
getV2gTpMessage(),
TimeRestrictions.getV2gEvccMsgTimeout(V2GMessages.SUPPORTED_APP_PROTOCOL_RES));
}
}
public Logger getLogger() {
return logger;
}
public void setLogger(Logger logger) {
this.logger = logger;
}
public MessageHandler getMessageHandler() {
return messageHandler;
}
public void setMessageHandler(MessageHandler messageHandler) {
this.messageHandler = messageHandler;
}
public int getSeccDiscoveryRequestCounter() {
return seccDiscoveryRequestCounter;
}
public void setSeccDiscoveryRequestCounter(int seccDiscoveryRequestCounter) {
this.seccDiscoveryRequestCounter = seccDiscoveryRequestCounter;
}
public byte getSecurity() {
return security;
}
public boolean isSecureCommunication() {
return Byte.compare(getSecurity(), GlobalValues.V2G_SECURITY_WITH_TLS.getByteValue()) == 0 ? true : false;
}
public void setSecurity(byte security) {
this.security = security;
getLogger().info("Security level " +
((Byte.compare(security, GlobalValues.V2G_SECURITY_WITH_TLS.getByteValue()) == 0) ? "TLS" : "TCP") +
" was chosen");
}
public V2GCommunicationSessionEVCC getV2gCommunicationSessionEVCC() {
return v2gCommunicationSessionEVCC;
}
public void setV2gCommunicationSessionEVCC(
V2GCommunicationSessionEVCC v2gCommunicationSessionEVCC) {
this.v2gCommunicationSessionEVCC = v2gCommunicationSessionEVCC;
}
public int getSessionRetryCounter() {
return sessionRetryCounter;
}
public void setSessionRetryCounter(int sessionRetryCounter) {
this.sessionRetryCounter = sessionRetryCounter;
}
public V2GTPMessage getV2gTpMessage() {
return v2gTpMessage;
}
public void setV2gTpMessage(V2GTPMessage v2gTpMessage) {
this.v2gTpMessage = v2gTpMessage;
}
public Thread getTransportLayerThread() {
return transportLayerThread;
}
public void setTransportLayerThread(Thread transportLayerThread) {
this.transportLayerThread = transportLayerThread;
}
public StatefulTransportLayerClient getTransportLayerClient() {
return transportLayerClient;
}
public void setTransportLayerClient(StatefulTransportLayerClient transportLayerClient) {
this.transportLayerClient = transportLayerClient;
}
}

View File

@@ -0,0 +1,471 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.evcc.states;
import java.security.KeyStore;
import java.util.Arrays;
import java.util.ListIterator;
import javax.xml.bind.JAXBElement;
import javax.xml.namespace.QName;
import com.v2gclarity.risev2g.evcc.evController.IACEVController;
import com.v2gclarity.risev2g.evcc.evController.IDCEVController;
import com.v2gclarity.risev2g.evcc.session.V2GCommunicationSessionEVCC;
import com.v2gclarity.risev2g.shared.enumerations.CPStates;
import com.v2gclarity.risev2g.shared.enumerations.GlobalValues;
import com.v2gclarity.risev2g.shared.enumerations.V2GMessages;
import com.v2gclarity.risev2g.shared.misc.State;
import com.v2gclarity.risev2g.shared.utils.ByteUtils;
import com.v2gclarity.risev2g.shared.utils.MiscUtils;
import com.v2gclarity.risev2g.shared.utils.SecurityUtils;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.AuthorizationReqType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.AuthorizationResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.BodyBaseType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.CableCheckReqType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.CableCheckResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.CertificateInstallationResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.CertificateUpdateResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargeParameterDiscoveryReqType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargeParameterDiscoveryResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargeProgressType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargingProfileType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargingSessionType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargingStatusResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.CurrentDemandReqType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.CurrentDemandResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.DCEVPowerDeliveryParameterType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.EMAIDType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.EnergyTransferModeType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.MessageHeaderType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.MeteringReceiptResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PaymentDetailsReqType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PaymentDetailsResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PaymentOptionType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PaymentServiceSelectionReqType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PaymentServiceSelectionResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PowerDeliveryReqType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PowerDeliveryResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PreChargeResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ResponseCodeType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ServiceDetailReqType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ServiceDetailResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ServiceDiscoveryResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.SessionSetupResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.SessionStopReqType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.SessionStopResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.V2GMessage;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.WeldingDetectionResType;
/**
* Some request messages are to be sent from different states which makes it more convenient (having
* less code and being less error-prone) to keep the creation of those messages in one single class.
*/
public abstract class ClientState extends State {
public ClientState(V2GCommunicationSessionEVCC commSessionContext) {
super(commSessionContext);
}
public V2GCommunicationSessionEVCC getCommSessionContext() {
return (V2GCommunicationSessionEVCC) super.getCommSessionContext();
}
protected boolean isIncomingMessageValid(Object incomingMessage, Class<? extends BodyBaseType> expectedMessage) {
V2GMessage v2gMessage = null;
if (incomingMessage instanceof V2GMessage) {
v2gMessage = (V2GMessage) incomingMessage;
if (!expectedMessage.isAssignableFrom(v2gMessage.getBody().getBodyElement().getValue().getClass())) {
getLogger().fatal("Invalid message (" + v2gMessage.getBody().getBodyElement().getValue().getClass().getSimpleName() +
") at this state (" + this.getClass().getSimpleName() + ")");
return false;
} else {
getLogger().debug(v2gMessage.getBody().getBodyElement().getValue().getClass().getSimpleName().replace("Type", "") + " received");
if (!isHeaderOK(v2gMessage.getHeader())) return false;
if (!isResponseCodeOK(v2gMessage)) return false;
return true;
}
} else {
getLogger().fatal("Incoming message is not a V2GMessage");
return false;
}
}
/**
* Performs the following checks:
* - is the returned session ID the same as the one saved by the EVCC?
* - does the EVCC need to react to a possibly set notification?
* - does the EVCC need to check the signature?
*
* @param header The header of the V2GMessage
*/
private boolean isHeaderOK(MessageHeaderType header) {
// Check sessionID (only if not at state WaitForSessionSetupRes)
if (!this.equals(getCommSessionContext().getStates().get(V2GMessages.SESSION_SETUP_RES)) &&
!Arrays.equals(header.getSessionID(), getCommSessionContext().getSessionID())) {
getLogger().error("Session ID is invalid: " +
"expected " + ByteUtils.toLongFromByteArray(getCommSessionContext().getSessionID()) +
", received " + ByteUtils.toLongFromByteArray(header.getSessionID()));
return false;
}
if (header.getNotification() != null) {
// TODO react on the several notifications
}
/*
* If a signature is present, it is placed in the header. However, not all messages have a
* signature. Therefore, the signature validation is to be done in the respective state itself.
*/
return true;
}
private boolean isResponseCodeOK(V2GMessage responseMessage) {
BodyBaseType bbt = ((V2GMessage) responseMessage).getBody().getBodyElement().getValue();
ResponseCodeType v2gMessageRCT = null;
switch (bbt.getClass().getSimpleName()) {
case "SessionSetupResType":
v2gMessageRCT = ((SessionSetupResType) bbt).getResponseCode();
break;
case "ServiceDiscoveryResType":
v2gMessageRCT = ((ServiceDiscoveryResType) bbt).getResponseCode();
break;
case "ServiceDetailResType":
v2gMessageRCT = ((ServiceDetailResType) bbt).getResponseCode();
break;
case "PaymentServiceSelectionResType":
v2gMessageRCT = ((PaymentServiceSelectionResType) bbt).getResponseCode();
break;
case "PaymentDetailsResType":
v2gMessageRCT = ((PaymentDetailsResType) bbt).getResponseCode();
break;
case "CertificateInstallationResType":
v2gMessageRCT = ((CertificateInstallationResType) bbt).getResponseCode();
break;
case "CertificateUpdateResType":
v2gMessageRCT = ((CertificateUpdateResType) bbt).getResponseCode();
break;
case "AuthorizationResType":
v2gMessageRCT = ((AuthorizationResType) bbt).getResponseCode();
break;
case "ChargeParameterDiscoveryResType":
v2gMessageRCT = ((ChargeParameterDiscoveryResType) bbt).getResponseCode();
break;
case "CableCheckResType":
v2gMessageRCT = ((CableCheckResType) bbt).getResponseCode();
break;
case "PreChargeResType":
v2gMessageRCT = ((PreChargeResType) bbt).getResponseCode();
break;
case "PowerDeliveryResType":
v2gMessageRCT = ((PowerDeliveryResType) bbt).getResponseCode();
break;
case "ChargingStatusResType":
v2gMessageRCT = ((ChargingStatusResType) bbt).getResponseCode();
break;
case "CurrentDemandResType":
v2gMessageRCT = ((CurrentDemandResType) bbt).getResponseCode();
break;
case "MeteringReceiptResType":
v2gMessageRCT = ((MeteringReceiptResType) bbt).getResponseCode();
break;
case "WeldingDetectionResType":
v2gMessageRCT = ((WeldingDetectionResType) bbt).getResponseCode();
break;
case "SessionStopResType":
v2gMessageRCT = ((SessionStopResType) bbt).getResponseCode();
break;
default:
getLogger().error("Response message could not be identified");
return false;
}
if (v2gMessageRCT.toString().startsWith("OK")) return true;
else {
getLogger().error("Negative response code " + v2gMessageRCT.toString());
return false;
}
}
/**
* A ServiceDetailReq needs to be generated from several states:
* - WaitForServiceDiscoveryRes
* - WaitForServiceDetailRes
*
* Checks if the list of value added services (VAS) which are to be used contains service IDs. Those
* service IDs can be used in a ServiceDetailReq to request more details about the service.
* Each time a ServiceDetailReq is created, the respective service ID is deleted from the list.
*
* @return A ServiceDetailReq with a service ID whose details are requested, if the list of service IDs
* is not empty. Null otherwise.
*/
protected ServiceDetailReqType getServiceDetailReq() {
if (getCommSessionContext().getServiceDetailsToBeRequested().size() > 0) {
ListIterator<Integer> listIterator = getCommSessionContext().getServiceDetailsToBeRequested().listIterator();
ServiceDetailReqType serviceDetailReq = new ServiceDetailReqType();
serviceDetailReq.setServiceID(listIterator.next());
listIterator.remove();
return serviceDetailReq;
}
return null;
}
/**
* A ServiceDetailReq needs to be generated from several states:
* - WaitForServiceDiscoveryRes
* - WaitForServiceDetailRes
*/
protected PaymentServiceSelectionReqType paymentServiceSelectionReq() {
PaymentServiceSelectionReqType paymentServiceSelectionReq = new PaymentServiceSelectionReqType();
paymentServiceSelectionReq.setSelectedPaymentOption(getCommSessionContext().getSelectedPaymentOption());
return paymentServiceSelectionReq;
}
/**
* An AuthorizationReq needs to be generated from several states:
* - WaitForPaymentServiceSelectionRes (no genChallege)
* - WaitForPaymentDetailsRes (genChallenge)
* - WaitForAuthorizationRes (no genChallenge, EVSE is still processing)
*
* @return An AuthorizationReq, either empty or with a set genChallenge and ID depending on input parameter
*/
protected AuthorizationReqType getAuthorizationReq(byte[] genChallenge) {
AuthorizationReqType authorizationReq = new AuthorizationReqType();
if (genChallenge != null) {
authorizationReq.setGenChallenge(genChallenge);
/*
* Experience from the test symposium in San Diego (April 2016):
* The Id element of the signature is not restricted in size by the standard itself. But on embedded
* systems, the memory is very limited which is why we should not use long IDs for the signature reference
* element. A good size would be 3 characters max (like the example in the ISO 15118-2 annex J)
*/
authorizationReq.setId("ID1");
}
return authorizationReq;
}
/**
* A CableCheckReq needs to be generated from several states:
* - WaitForChargeParameterDiscoveryRes
* - WaitForCableCheckRes (EVSEProcessing = ONGOING)
*
* @return A CableCheckReq
*/
protected CableCheckReqType getCableCheckReq() {
CableCheckReqType cableCheckReq = new CableCheckReqType();
cableCheckReq.setDCEVStatus(((IDCEVController) getCommSessionContext().getEvController()).getDCEVStatus());
return cableCheckReq;
}
/**
* A CurrentDemandReq needs to be generated from several states:
* - WaitForCurrentDemandRes (the initial CurrentDemandReq message)
* - WaitForMeteringReceiptRes
*
* @return A CurrentDemandReq message
*/
protected CurrentDemandReqType getCurrentDemandReq() {
IDCEVController evController = (IDCEVController) getCommSessionContext().getEvController();
CurrentDemandReqType currentDemandReq = new CurrentDemandReqType();
currentDemandReq.setBulkChargingComplete(evController.isBulkChargingComplete());
currentDemandReq.setChargingComplete(evController.isChargingComplete());
currentDemandReq.setDCEVStatus(evController.getDCEVStatus());
currentDemandReq.setEVMaximumCurrentLimit(evController.getMaximumCurrentLimit());
currentDemandReq.setEVMaximumPowerLimit(evController.getMaximumPowerLimit());
currentDemandReq.setEVMaximumVoltageLimit(evController.getMaximumVoltageLimit());
currentDemandReq.setEVTargetCurrent(evController.getTargetCurrent());
currentDemandReq.setEVTargetVoltage(evController.getTargetVoltage());
currentDemandReq.setRemainingTimeToBulkSoC(evController.getRemainingTimeToBulkSOC());
currentDemandReq.setRemainingTimeToFullSoC(evController.getRemainingTimeToFullSOC());
return currentDemandReq;
}
/**
* A ChargeParameterDiscoveryReq needs to be generated from several states:
* - WaitForAuthorizationRes (the initial ChargeParameterDiscoveryReq)
* - WaitForPowerDeliveryRes (in case AC_EVSEStatus requests a renegotiation)
* - WaitForChargingStatusRes (in case AC_EVSEStatus requests a renegotiation)
*
* @return A ChargeParameterDiscoveryReq which itself consists of several complex datatypes.
*/
protected ChargeParameterDiscoveryReqType getChargeParameterDiscoveryReq() {
ChargeParameterDiscoveryReqType chargeParameterDiscoveryReq = new ChargeParameterDiscoveryReqType();
// Optionally limit the number of entries in the SAScheduleTuple by setting MaxEntriesSAScheduleTuple
chargeParameterDiscoveryReq.setRequestedEnergyTransferMode(getRequestedEnergyTransferMode());
if (getCommSessionContext().getRequestedEnergyTransferMode().toString().startsWith("AC"))
chargeParameterDiscoveryReq.setEVChargeParameter(((IACEVController) getCommSessionContext().getEvController()).getACEVChargeParamter());
else
chargeParameterDiscoveryReq.setEVChargeParameter(((IDCEVController) getCommSessionContext().getEvController()).getDCEVChargeParamter());
return chargeParameterDiscoveryReq;
}
/**
* A PaymentServiceSelectionReq needs to be generated from several states:
* - WaitForServiceDiscoveryRes
* - WaitForServiceDetailRes
*
* @return A PaymentServiceSelectionReq
*/
protected PaymentServiceSelectionReqType getPaymentServiceSelectionReq() {
PaymentServiceSelectionReqType paymentServiceSelectionReq = new PaymentServiceSelectionReqType();
paymentServiceSelectionReq.setSelectedPaymentOption(getCommSessionContext().getSelectedPaymentOption());
paymentServiceSelectionReq.setSelectedServiceList(getCommSessionContext().getSelectedServices());
return paymentServiceSelectionReq;
}
/**
* A PaymentDetailsReq needs to be generated from several states:
* - WaitForPaymentServiceSelectionRes
* - WaitForCertificateInstallationRes
* - WaitForCertificateUpdateRes
*
* @return A PaymentDetailsReq
*/
protected PaymentDetailsReqType getPaymentDetailsReq() {
KeyStore evccKeyStore = SecurityUtils.getKeyStore(
GlobalValues.EVCC_KEYSTORE_FILEPATH.toString(),
GlobalValues.PASSPHRASE_FOR_CERTIFICATES_AND_KEYS.toString());
PaymentDetailsReqType paymentDetailsReq = new PaymentDetailsReqType();
EMAIDType emaid = SecurityUtils.getEMAID(GlobalValues.PASSPHRASE_FOR_CERTIFICATES_AND_KEYS.toString());
if (emaid != null) {
paymentDetailsReq.setEMAID(SecurityUtils.getEMAID(GlobalValues.PASSPHRASE_FOR_CERTIFICATES_AND_KEYS.toString()).getValue());
paymentDetailsReq.setContractSignatureCertChain(SecurityUtils.getCertificateChain(
evccKeyStore, GlobalValues.ALIAS_CONTRACT_CERTIFICATE.toString()));
}
return paymentDetailsReq;
}
/**
* A PowerDeliveryReq needs to be generated from several states:
* - WaitForChargeParameterDiscoveryRes
* - WaitForChargingStatusRes
* - WaitForMeteringReceiptRes
*
* @param chargeProgress Indicates whether to START a charging session, RENEGOTIATE charing parameters
* or STOP the charging session
* @return A ChargeParameterDiscoveryReq which itself consists of several complex datatypes.
*/
protected PowerDeliveryReqType getPowerDeliveryReq(ChargeProgressType chargeProgress) {
PowerDeliveryReqType powerDeliveryReq = new PowerDeliveryReqType();
if (chargeProgress.equals(ChargeProgressType.START)) {
// Signal needed state change after sending PowerDeliveryReq in AC charging mode
if (getCommSessionContext().getRequestedEnergyTransferMode().toString().startsWith("AC"))
getCommSessionContext().setChangeToState(CPStates.STATE_C);
ChargingProfileType chargingProfile = getCommSessionContext().getEvController().getChargingProfile();
powerDeliveryReq.setChargingProfile(chargingProfile);
getCommSessionContext().setChargingProfile(chargingProfile);
} else if (chargeProgress.equals(ChargeProgressType.STOP)) {
// Signal needed state change after sending PowerDeliveryReq in AC charging mode
if (getCommSessionContext().getRequestedEnergyTransferMode().toString().startsWith("AC"))
getCommSessionContext().setChangeToState(CPStates.STATE_B);
}
powerDeliveryReq.setChargeProgress(chargeProgress);
powerDeliveryReq.setSAScheduleTupleID(getCommSessionContext().getEvController().getChosenSAScheduleTupleID());
// Set DC_EVPowerDeliveryParameter if in DC charging mode
if (getCommSessionContext().getRequestedEnergyTransferMode().toString().startsWith("DC")) {
/*
* The MessageHandler method getJAXBElement() cannot be used here because of the difference in the
* class name (DCEVPowerDeliveryParameter) and the name in the XSD (DC_EVPowerDeliveryParameter)
*/
JAXBElement<DCEVPowerDeliveryParameterType> jaxbDcEvPowerDeliveryParameter = new JAXBElement<>(new QName("urn:iso:15118:2:2013:MsgDataTypes", "DC_EVPowerDeliveryParameter"),
DCEVPowerDeliveryParameterType.class,
((IDCEVController) getCommSessionContext().getEvController()).getEVPowerDeliveryParameter());
powerDeliveryReq.setEVPowerDeliveryParameter(jaxbDcEvPowerDeliveryParameter);
}
return powerDeliveryReq;
}
/**
* A SessionStopReq needs to be generated from several states:
* - WaitForPowerDeliveryRes
* - WaitForWeldingDetectionRes
*
* @return A SessionStopReq message
*/
protected SessionStopReqType getSessionStopReq(ChargingSessionType chargingSessionType) {
SessionStopReqType sessionStopReq = new SessionStopReqType();
sessionStopReq.setChargingSession(chargingSessionType);
return sessionStopReq;
}
protected EnergyTransferModeType getRequestedEnergyTransferMode() {
EnergyTransferModeType 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();
// 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;
}
}

View File

@@ -0,0 +1,98 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.evcc.states;
import java.util.concurrent.TimeUnit;
import com.v2gclarity.risev2g.evcc.session.V2GCommunicationSessionEVCC;
import com.v2gclarity.risev2g.shared.enumerations.GlobalValues;
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.SecurityUtils;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.AuthorizationReqType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.AuthorizationResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargeParameterDiscoveryReqType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.EVSEProcessingType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PaymentOptionType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.V2GMessage;
public class WaitForAuthorizationRes extends ClientState {
public WaitForAuthorizationRes(V2GCommunicationSessionEVCC commSessionContext) {
super(commSessionContext);
}
@Override
public ReactionToIncomingMessage processIncomingMessage(Object message) {
if (isIncomingMessageValid(message, AuthorizationResType.class)) {
V2GMessage v2gMessageRes = (V2GMessage) message;
AuthorizationResType authorizationRes =
(AuthorizationResType) v2gMessageRes.getBody().getBodyElement().getValue();
if (authorizationRes.getEVSEProcessing() == null)
return new TerminateSession("EVSEProcessing parameter of AuthorizationRes is null. Parameter is mandatory.");
if (authorizationRes.getEVSEProcessing().equals(EVSEProcessingType.FINISHED)) {
getLogger().debug("EVSEProcessing was set to FINISHED");
getCommSessionContext().setOngoingTimer(0L);
getCommSessionContext().setOngoingTimerActive(false);
ChargeParameterDiscoveryReqType chargeParameterDiscoveryReq = getChargeParameterDiscoveryReq();
/*
* Save this request in case the ChargeParameterDiscoveryRes indicates that the EVSE is
* still processing. Then this request can just be resent instead of asking the EV again.
*/
getCommSessionContext().setChargeParameterDiscoveryReq(chargeParameterDiscoveryReq);
return getSendMessage(chargeParameterDiscoveryReq, V2GMessages.CHARGE_PARAMETER_DISCOVERY_RES);
} else {
getLogger().debug("EVSEProcessing was set to ONGOING");
long elapsedTimeInMs = 0;
if (getCommSessionContext().isOngoingTimerActive()) {
long elapsedTime = System.nanoTime() - getCommSessionContext().getOngoingTimer();
elapsedTimeInMs = TimeUnit.MILLISECONDS.convert(elapsedTime, TimeUnit.NANOSECONDS);
if (elapsedTimeInMs > TimeRestrictions.V2G_EVCC_ONGOING_TIMEOUT)
return new TerminateSession("Ongoing timer timed out for AuthorizationReq");
} else {
getCommSessionContext().setOngoingTimer(System.nanoTime());
getCommSessionContext().setOngoingTimerActive(true);
}
// [V2G2-684] demands to send an empty AuthorizationReq if the field EVSEProcessing is set to 'Ongoing'
AuthorizationReqType authorizationReq = getAuthorizationReq(null);
return getSendMessage(authorizationReq, V2GMessages.AUTHORIZATION_RES, Math.min((TimeRestrictions.V2G_EVCC_ONGOING_TIMEOUT - (int) elapsedTimeInMs), TimeRestrictions.getV2gEvccMsgTimeout(V2GMessages.AUTHORIZATION_RES)));
}
} else {
return new TerminateSession("Incoming message raised an error");
}
}
}

View File

@@ -0,0 +1,88 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.evcc.states;
import java.util.concurrent.TimeUnit;
import com.v2gclarity.risev2g.evcc.evController.IDCEVController;
import com.v2gclarity.risev2g.evcc.session.V2GCommunicationSessionEVCC;
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.v2gMessages.msgDef.CableCheckResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.EVSEProcessingType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PreChargeReqType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.V2GMessage;
public class WaitForCableCheckRes extends ClientState {
public WaitForCableCheckRes(V2GCommunicationSessionEVCC commSessionContext) {
super(commSessionContext);
}
@Override
public ReactionToIncomingMessage processIncomingMessage(Object message) {
if (isIncomingMessageValid(message, CableCheckResType.class)) {
V2GMessage v2gMessageRes = (V2GMessage) message;
CableCheckResType cableCheckRes =
(CableCheckResType) v2gMessageRes.getBody().getBodyElement().getValue();
if (cableCheckRes.getEVSEProcessing().equals(EVSEProcessingType.FINISHED)) {
getLogger().debug("EVSEProcessing was set to FINISHED");
IDCEVController dcEvController = (IDCEVController) getCommSessionContext().getEvController();
PreChargeReqType preChargeReq = new PreChargeReqType();
preChargeReq.setDCEVStatus(dcEvController.getDCEVStatus());
preChargeReq.setEVTargetCurrent(dcEvController.getTargetCurrent());
preChargeReq.setEVTargetVoltage(dcEvController.getTargetVoltage());
getCommSessionContext().setOngoingTimer(System.nanoTime());
getCommSessionContext().setOngoingTimerActive(true);
return getSendMessage(preChargeReq, V2GMessages.PRE_CHARGE_RES);
} else {
getLogger().debug("EVSEProcessing was set to ONGOING");
long elapsedTimeInMs = 0;
if (getCommSessionContext().isOngoingTimerActive()) {
long elapsedTime = System.nanoTime() - getCommSessionContext().getOngoingTimer();
elapsedTimeInMs = TimeUnit.MILLISECONDS.convert(elapsedTime, TimeUnit.NANOSECONDS);
if (elapsedTimeInMs > TimeRestrictions.V2G_EVCC_CABLE_CHECK_TIMEOUT)
return new TerminateSession("CableCheck timer timed out for CableCheckReq");
} else {
getCommSessionContext().setOngoingTimer(System.nanoTime());
getCommSessionContext().setOngoingTimerActive(true);
}
return getSendMessage(getCableCheckReq(), V2GMessages.CABLE_CHECK_RES, Math.min((TimeRestrictions.V2G_EVCC_CABLE_CHECK_TIMEOUT - (int) elapsedTimeInMs), TimeRestrictions.getV2gEvccMsgTimeout(V2GMessages.CABLE_CHECK_RES)));
}
} else {
return new TerminateSession("Incoming message raised an error");
}
}
}

View File

@@ -0,0 +1,125 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.evcc.states;
import java.security.interfaces.ECPrivateKey;
import java.util.HashMap;
import com.v2gclarity.risev2g.evcc.session.V2GCommunicationSessionEVCC;
import com.v2gclarity.risev2g.shared.enumerations.GlobalValues;
import com.v2gclarity.risev2g.shared.enumerations.PKI;
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.SecurityUtils;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.CertificateInstallationResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ResponseCodeType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.SignatureType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.V2GMessage;
public class WaitForCertificateInstallationRes extends ClientState {
public WaitForCertificateInstallationRes(V2GCommunicationSessionEVCC commSessionContext) {
super(commSessionContext);
}
@Override
public ReactionToIncomingMessage processIncomingMessage(Object message) {
if (isIncomingMessageValid(message, CertificateInstallationResType.class)) {
V2GMessage v2gMessageRes = (V2GMessage) message;
CertificateInstallationResType certificateInstallationRes =
(CertificateInstallationResType) v2gMessageRes.getBody().getBodyElement().getValue();
if (!verifySignature(certificateInstallationRes, v2gMessageRes.getHeader().getSignature())) {
return new TerminateSession("Signature verification failed");
}
// Check complete CPS certificate chain
ResponseCodeType certChainResponseCode = SecurityUtils.verifyCertificateChain(
certificateInstallationRes.getSAProvisioningCertificateChain(),
GlobalValues.EVCC_TRUSTSTORE_FILEPATH.toString(),
PKI.CPS);
if (!certChainResponseCode.equals(ResponseCodeType.OK)) {
return new TerminateSession("Provisioning certificate chain is not valid");
}
ECPrivateKey oemProvCertPrivateKey = SecurityUtils.getPrivateKey(
SecurityUtils.getKeyStore(
GlobalValues.EVCC_KEYSTORE_FILEPATH.toString(),
GlobalValues.PASSPHRASE_FOR_CERTIFICATES_AND_KEYS.toString()),
GlobalValues.ALIAS_OEM_PROV_CERTIFICATE.toString());
// Save contract certificate chain
if (!SecurityUtils.saveContractCertificateChain(
GlobalValues.PASSPHRASE_FOR_CERTIFICATES_AND_KEYS.toString(),
certificateInstallationRes.getContractSignatureCertChain(),
SecurityUtils.decryptContractCertPrivateKey(
certificateInstallationRes.getDHpublickey().getValue(),
certificateInstallationRes.getContractSignatureEncryptedPrivateKey().getValue(),
oemProvCertPrivateKey))) {
return new TerminateSession("Contract certificate chain could not be saved");
}
return getSendMessage(getPaymentDetailsReq(), V2GMessages.PAYMENT_DETAILS_RES);
} else {
return new TerminateSession("Incoming message raised an error");
}
}
private boolean verifySignature(CertificateInstallationResType certificateInstallationRes, SignatureType signature) {
HashMap<String, byte[]> verifyXMLSigRefElements = new HashMap<String, byte[]>();
verifyXMLSigRefElements.put(
certificateInstallationRes.getContractSignatureCertChain().getId(),
SecurityUtils.generateDigest(
certificateInstallationRes.getContractSignatureCertChain().getId(),
getMessageHandler().getJaxbElement(certificateInstallationRes.getContractSignatureCertChain())));
verifyXMLSigRefElements.put(
certificateInstallationRes.getContractSignatureEncryptedPrivateKey().getId(),
SecurityUtils.generateDigest(
certificateInstallationRes.getContractSignatureEncryptedPrivateKey().getId(),
getMessageHandler().getJaxbElement(certificateInstallationRes.getContractSignatureEncryptedPrivateKey())));
verifyXMLSigRefElements.put(
certificateInstallationRes.getDHpublickey().getId(),
SecurityUtils.generateDigest(
certificateInstallationRes.getDHpublickey().getId(),
getMessageHandler().getJaxbElement(certificateInstallationRes.getDHpublickey())));
verifyXMLSigRefElements.put(
certificateInstallationRes.getEMAID().getId(),
SecurityUtils.generateDigest(
certificateInstallationRes.getEMAID().getId(),
getMessageHandler().getJaxbElement(certificateInstallationRes.getEMAID())));
if (!SecurityUtils.verifySignature(
signature,
getMessageHandler().getJaxbElement(signature.getSignedInfo()),
verifyXMLSigRefElements,
certificateInstallationRes.getSAProvisioningCertificateChain().getCertificate())) {
return false;
}
return true;
}
}

View File

@@ -0,0 +1,124 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.evcc.states;
import java.security.interfaces.ECPrivateKey;
import java.util.HashMap;
import com.v2gclarity.risev2g.evcc.session.V2GCommunicationSessionEVCC;
import com.v2gclarity.risev2g.shared.enumerations.GlobalValues;
import com.v2gclarity.risev2g.shared.enumerations.PKI;
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.SecurityUtils;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.CertificateUpdateResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ResponseCodeType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.SignatureType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.V2GMessage;
public class WaitForCertificateUpdateRes extends ClientState {
public WaitForCertificateUpdateRes(V2GCommunicationSessionEVCC commSessionContext) {
super(commSessionContext);
}
@Override
public ReactionToIncomingMessage processIncomingMessage(Object message) {
if (isIncomingMessageValid(message, CertificateUpdateResType.class)) {
V2GMessage v2gMessageRes = (V2GMessage) message;
CertificateUpdateResType certificateUpdateRes =
(CertificateUpdateResType) v2gMessageRes.getBody().getBodyElement().getValue();
if (!verifySignature(certificateUpdateRes, v2gMessageRes.getHeader().getSignature())) {
return new TerminateSession("Signature verification failed");
}
// Check complete CPS certificate chain
ResponseCodeType certChainResponseCode = SecurityUtils.verifyCertificateChain(
certificateUpdateRes.getSAProvisioningCertificateChain(),
GlobalValues.EVCC_TRUSTSTORE_FILEPATH.toString(),
PKI.CPS);
if (!certChainResponseCode.equals(ResponseCodeType.OK)) {
return new TerminateSession("Provisioning certificate chain is not valid");
}
ECPrivateKey contractCertPrivateKey = SecurityUtils.getPrivateKey(
SecurityUtils.getKeyStore(
GlobalValues.EVCC_KEYSTORE_FILEPATH.toString(),
GlobalValues.PASSPHRASE_FOR_CERTIFICATES_AND_KEYS.toString()),
GlobalValues.ALIAS_CONTRACT_CERTIFICATE.toString());
// Save contract certificate chain
if (!SecurityUtils.saveContractCertificateChain(
GlobalValues.PASSPHRASE_FOR_CERTIFICATES_AND_KEYS.toString(),
certificateUpdateRes.getContractSignatureCertChain(),
SecurityUtils.decryptContractCertPrivateKey(
certificateUpdateRes.getDHpublickey().getValue(),
certificateUpdateRes.getContractSignatureEncryptedPrivateKey().getValue(),
contractCertPrivateKey))) {
return new TerminateSession("Contract certificate chain could not be saved");
}
return getSendMessage(getPaymentDetailsReq(), V2GMessages.PAYMENT_DETAILS_RES);
} else {
return new TerminateSession("Incoming message raised an error");
}
}
private boolean verifySignature(CertificateUpdateResType certificateUpdateRes, SignatureType signature) {
HashMap<String, byte[]> verifyXMLSigRefElements = new HashMap<String, byte[]>();
verifyXMLSigRefElements.put(
certificateUpdateRes.getContractSignatureCertChain().getId(),
SecurityUtils.generateDigest(
certificateUpdateRes.getContractSignatureCertChain().getId(),
getMessageHandler().getJaxbElement(certificateUpdateRes.getContractSignatureCertChain())));
verifyXMLSigRefElements.put(
certificateUpdateRes.getContractSignatureEncryptedPrivateKey().getId(),
SecurityUtils.generateDigest(
certificateUpdateRes.getContractSignatureEncryptedPrivateKey().getId(),
getMessageHandler().getJaxbElement(certificateUpdateRes.getContractSignatureEncryptedPrivateKey())));
verifyXMLSigRefElements.put(
certificateUpdateRes.getDHpublickey().getId(),
SecurityUtils.generateDigest(
certificateUpdateRes.getDHpublickey().getId(),
getMessageHandler().getJaxbElement(certificateUpdateRes.getDHpublickey())));
verifyXMLSigRefElements.put(
certificateUpdateRes.getEMAID().getId(),
SecurityUtils.generateDigest(
certificateUpdateRes.getEMAID().getId(),
getMessageHandler().getJaxbElement(certificateUpdateRes.getEMAID())));
if (!SecurityUtils.verifySignature(
signature,
getMessageHandler().getJaxbElement(signature.getSignedInfo()),
verifyXMLSigRefElements,
certificateUpdateRes.getSAProvisioningCertificateChain().getCertificate())) {
return false;
}
return true;
}
}

View File

@@ -0,0 +1,276 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.evcc.states;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.TimeUnit;
import com.v2gclarity.risev2g.evcc.session.V2GCommunicationSessionEVCC;
import com.v2gclarity.risev2g.shared.enumerations.CPStates;
import com.v2gclarity.risev2g.shared.enumerations.GlobalValues;
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.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;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.SAScheduleListType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.SAScheduleTupleType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.SignatureType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.V2GMessage;
public class WaitForChargeParameterDiscoveryRes extends ClientState {
public WaitForChargeParameterDiscoveryRes(V2GCommunicationSessionEVCC commSessionContext) {
super(commSessionContext);
}
@Override
public ReactionToIncomingMessage processIncomingMessage(Object message) {
if (isIncomingMessageValid(message, ChargeParameterDiscoveryResType.class)) {
V2GMessage v2gMessageRes = (V2GMessage) message;
ChargeParameterDiscoveryResType chargeParameterDiscoveryRes =
(ChargeParameterDiscoveryResType) v2gMessageRes.getBody().getBodyElement().getValue();
if (chargeParameterDiscoveryRes.getEVSEProcessing() == null)
return new TerminateSession("EVSEProcessing field of ChargeParameterDiscoveryRes is null. This field is mandatory.");
if (chargeParameterDiscoveryRes.getEVSEProcessing().equals(EVSEProcessingType.ONGOING)) {
getLogger().debug("EVSEProcessing was set to ONGOING");
long elapsedTimeInMs = 0;
if (getCommSessionContext().isOngoingTimerActive()) {
long elapsedTime = System.nanoTime() - getCommSessionContext().getOngoingTimer();
elapsedTimeInMs = TimeUnit.MILLISECONDS.convert(elapsedTime, TimeUnit.NANOSECONDS);
if (elapsedTimeInMs > TimeRestrictions.V2G_EVCC_ONGOING_TIMEOUT)
return new TerminateSession("Ongoing timer timed out for ChargeParameterDiscoveryReq");
} else {
getCommSessionContext().setOngoingTimer(System.nanoTime());
getCommSessionContext().setOngoingTimerActive(true);
}
return getSendMessage(getCommSessionContext().getChargeParameterDiscoveryReq(), V2GMessages.CHARGE_PARAMETER_DISCOVERY_RES, Math.min((TimeRestrictions.V2G_EVCC_ONGOING_TIMEOUT - (int) elapsedTimeInMs), TimeRestrictions.getV2gEvccMsgTimeout(V2GMessages.CHARGE_PARAMETER_DISCOVERY_RES)));
} else {
getLogger().debug("EVSEProcessing was set to FINISHED");
getCommSessionContext().setOngoingTimer(0L);
getCommSessionContext().setOngoingTimerActive(false);
// Check for the EVSENotification
EVSENotificationType evseNotification = null;
try {
if (getCommSessionContext().getRequestedEnergyTransferMode().toString().startsWith("AC"))
evseNotification = ((ACEVSEChargeParameterType) chargeParameterDiscoveryRes
.getEVSEChargeParameter().getValue())
.getACEVSEStatus().getEVSENotification();
else
evseNotification = ((DCEVSEChargeParameterType) chargeParameterDiscoveryRes
.getEVSEChargeParameter().getValue())
.getDCEVSEStatus().getEVSENotification();
} catch (ClassCastException e) {
return new TerminateSession("Sent EVSEChargeParameter do not match requested energy transfer mode " +
getCommSessionContext().getRequestedEnergyTransferMode().toString());
}
if (evseNotification.equals(EVSENotificationType.STOP_CHARGING)) {
getLogger().debug("The EVSE requested to stop the charging process");
getCommSessionContext().setChargingSession(ChargingSessionType.TERMINATE);
return getSendMessage(getPowerDeliveryReq(ChargeProgressType.STOP), V2GMessages.POWER_DELIVERY_RES);
} else {
/*
* The case RE_NEGOTIATION is to be ignored according to [V2G2-841] and [V2G2-680].
* An SECC triggered renegotiation is only to be reacted on in the messages
* - ChargingStatusRes
* - MeteringReceiptRes
* - CurrentDemandRes
*/
if (chargeParameterDiscoveryRes.getSASchedules() == null)
return new TerminateSession("SASchedules field of ChargeParameterDiscoveryRes is null although EVSEProcessing is set to FINISHED. SASchedules is mandatory in this case.");
SAScheduleListType saSchedules = (SAScheduleListType) chargeParameterDiscoveryRes.getSASchedules().getValue();
// If TLS is used, verify each sales tariff (if present) with the mobility operator sub 2 certificate
if (getCommSessionContext().isTlsConnection() && saSchedules != null) {
if (!verifySalesTariffs(saSchedules, v2gMessageRes.getHeader().getSignature())) {
getLogger().warn("The SalesTariff will be ignored for the charge process due to "
+ "failed signature verification during TLS communication.");
deleteUnverifiedSalesTariffs(saSchedules);
}
}
// Save the list of SASchedules (saves the time of reception as well)
getCommSessionContext().setSaSchedules(saSchedules);
/*
* 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")) {
// CP state C signaling BEFORE sending CableCheckReq message in DC
if (getCommSessionContext().getEvController().setCPState(CPStates.STATE_C)) {
// Set timer for CableCheck
getCommSessionContext().setOngoingTimer(System.nanoTime());
getCommSessionContext().setOngoingTimerActive(true);
return getSendMessage(getCableCheckReq(), V2GMessages.CABLE_CHECK_RES);
} else
return new TerminateSession("CP state C not ready (current state = " +
getCommSessionContext().getEvController().getCPState() +
")");
} else {
return new TerminateSession("RequestedEnergyTransferMode '" + getCommSessionContext().getRequestedEnergyTransferMode().toString() +
"is neither of type AC nor DC");
}
} else {
return new TerminateSession("CP state B not ready (current state = " +
getCommSessionContext().getEvController().getCPState() +
")");
}
}
}
} else {
return new TerminateSession("Incoming message raised an error");
}
}
/**
* Verifies each sales tariff given with the ChargeParameterDiscoveryRes message with the
* mobility operator sub 2 certificate.
*
* @param saSchedules The SASchedule list which holds all PMaxSchedules and SalesTariffs
* @param signature The signature for the sales tariffs
* @return True, if the verification of the sales tariffs was successful, false otherwise
*/
private boolean verifySalesTariffs(SAScheduleListType saSchedules, SignatureType signature) {
/*
* Some important requirements:
*
* 1. In case of PnC, and if a SalesTariff is used by the secondary actor, the secondary actor SHALL
* sign the field SalesTariff of type SalesTariffType. In case of EIM, the secondary actor MAY sign
* this field.
*
* 2. If the EVCC treats the SalesTariff as invalid, it shall ignore the SalesTariff, i.e. the
* behavior of the EVCC shall be the same as if no SalesTariff was received. Furthermore, the
* EVCC MAY close the connection. It then may reopen the connection again.
*/
boolean salesTariffSignatureAvailable = (signature == null) ? false : true;
boolean ignoreSalesTariffs = (getCommSessionContext().isTlsConnection() && !salesTariffSignatureAvailable) ? true : false;
short ignoredSalesTariffs = 0;
HashMap<String, byte[]> verifyXMLSigRefElements = new HashMap<String, byte[]>();
List<SAScheduleTupleType> saScheduleTuples = saSchedules.getSAScheduleTuple();
int salesTariffCounter = 0;
for (SAScheduleTupleType saScheduleTuple : saScheduleTuples) {
// verification regards only sales tariffs, not PMaxSchedules
if (saScheduleTuple.getSalesTariff() == null) continue;
// Check if signature is given during TLS communication. If no signature is given, delete SalesTariff
if (ignoreSalesTariffs) {
ignoredSalesTariffs++;
saScheduleTuple.setSalesTariff(null);
continue;
}
salesTariffCounter++;
verifyXMLSigRefElements.put(
saScheduleTuple.getSalesTariff().getId(),
SecurityUtils.generateDigest(
saScheduleTuple.getSalesTariff().getId(),
getMessageHandler().getJaxbElement(saScheduleTuple.getSalesTariff())));
}
if (salesTariffCounter > 0) {
X509Certificate moSubCA2Certificate = SecurityUtils.getMOSubCA2Certificate(
GlobalValues.EVCC_KEYSTORE_FILEPATH.toString());
if (moSubCA2Certificate == null) {
getLogger().error("No MOSubCA2 certificate found, signature of SalesTariff could therefore not be verified");
return false;
} else {
if (!SecurityUtils.verifySignature(
signature,
getMessageHandler().getJaxbElement(signature.getSignedInfo()),
verifyXMLSigRefElements,
moSubCA2Certificate)) {
getLogger().warn("Verification of SalesTariff failed using certificate with distinguished name '" +
moSubCA2Certificate.getSubjectX500Principal().getName() + "'");
return false;
}
}
}
if (ignoredSalesTariffs > 0) {
getLogger().info("SalesTariffs could not be verified because of missing signature and will therefore be ignored");
return false;
}
return true;
}
/**
* If the signature of one ore more sales tariffs cannot be verified, then the sales tariffs should be ignored
* rather than terminating the charge process. The charge process can then proceed based solely on the
* PMaxSchedule
*
* @param saSchedules The schedule(s) from the secondary actor including PMaxSchedule and potential SalesTariff
* elements.
*/
private void deleteUnverifiedSalesTariffs(SAScheduleListType saSchedules) {
List<SAScheduleTupleType> saScheduleTuples = saSchedules.getSAScheduleTuple();
for (SAScheduleTupleType saScheduleTuple : saScheduleTuples) {
saScheduleTuple.setSalesTariff(null);
}
}
}

View File

@@ -0,0 +1,138 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.evcc.states;
import com.v2gclarity.risev2g.evcc.evController.IACEVController;
import com.v2gclarity.risev2g.evcc.session.V2GCommunicationSessionEVCC;
import com.v2gclarity.risev2g.shared.enumerations.GlobalValues;
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.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;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.V2GMessage;
public class WaitForChargingStatusRes extends ClientState {
public WaitForChargingStatusRes(V2GCommunicationSessionEVCC commSessionContext) {
super(commSessionContext);
}
@Override
public ReactionToIncomingMessage processIncomingMessage(Object message) {
if (isIncomingMessageValid(message, ChargingStatusResType.class)) {
V2GMessage v2gMessageRes = (V2GMessage) message;
ChargingStatusResType chargingStatusRes =
(ChargingStatusResType) v2gMessageRes.getBody().getBodyElement().getValue();
/*
* ReceiptRequired has higher priority than a possible EVSENotification=Renegotiate
*
* Check if communication is secured with TLS before reacting upon a possible request from the SECC to send
* a MeteringReceiptRequest. If no TLS is used, a MeteringReceiptRequest may not be sent because
* a signature cannot be applied without private key of the contract certificate.
*/
if (chargingStatusRes.isReceiptRequired() != null && chargingStatusRes.isReceiptRequired() && getCommSessionContext().isTlsConnection()) {
MeteringReceiptReqType meteringReceiptReq = new MeteringReceiptReqType();
/*
* Experience from the test symposium in San Diego (April 2016):
* The Id element of the signature is not restricted in size by the standard itself. But on embedded
* systems, the memory is very limited which is why we should not use long IDs for the signature reference
* element. A good size would be 3 characters max (like the example in the ISO 15118-2 annex J)
*/
meteringReceiptReq.setId("id1");
meteringReceiptReq.setMeterInfo(chargingStatusRes.getMeterInfo());
meteringReceiptReq.setSAScheduleTupleID(chargingStatusRes.getSAScheduleTupleID());
meteringReceiptReq.setSessionID(getCommSessionContext().getSessionID());
// Set xml reference element
getXMLSignatureRefElements().put(
meteringReceiptReq.getId(),
SecurityUtils.generateDigest(
meteringReceiptReq.getId(),
getMessageHandler().getJaxbElement(meteringReceiptReq)));
// Set signing private key
setSignaturePrivateKey(SecurityUtils.getPrivateKey(
SecurityUtils.getKeyStore(
GlobalValues.EVCC_KEYSTORE_FILEPATH.toString(),
GlobalValues.PASSPHRASE_FOR_CERTIFICATES_AND_KEYS.toString()),
GlobalValues.ALIAS_CONTRACT_CERTIFICATE.toString())
);
return getSendMessage(meteringReceiptReq, V2GMessages.METERING_RECEIPT_RES);
}
// Check for EVSEMaxCurrent and tell the EV
if (chargingStatusRes.getEVSEMaxCurrent() != null)
((IACEVController) getCommSessionContext().getEvController())
.adjustMaxCurrent(chargingStatusRes.getEVSEMaxCurrent());
switch (chargingStatusRes.getACEVSEStatus().getEVSENotification()) {
case STOP_CHARGING:
getCommSessionContext().setChargingSession(ChargingSessionType.TERMINATE);
return getSendMessage(getPowerDeliveryReq(ChargeProgressType.STOP),
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)");
default:
// TODO regard [V2G2-305] (new SalesTariff if EAmount not yet met and tariff finished)
if (getCommSessionContext().getEvController().isChargingLoopActive()) {
// Check whether or not the EV controller triggered a renegotiation
if (getCommSessionContext().isRenegotiationRequested()) {
return getSendMessage(getPowerDeliveryReq(ChargeProgressType.RENEGOTIATE),
V2GMessages.POWER_DELIVERY_RES,
" (ChargeProgress = RE_NEGOTIATION)");
} else {
ChargingStatusReqType chargingStatusReq = new ChargingStatusReqType();
return getSendMessage(chargingStatusReq, V2GMessages.CHARGING_STATUS_RES);
}
} else {
/* 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)");
}
}
} else {
return new TerminateSession("Incoming message raised an error");
}
}
}

View File

@@ -0,0 +1,131 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.evcc.states;
import com.v2gclarity.risev2g.evcc.session.V2GCommunicationSessionEVCC;
import com.v2gclarity.risev2g.shared.enumerations.GlobalValues;
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.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;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.EVSENotificationType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.MeteringReceiptReqType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.V2GMessage;
public class WaitForCurrentDemandRes extends ClientState {
public WaitForCurrentDemandRes(V2GCommunicationSessionEVCC commSessionContext) {
super(commSessionContext);
}
@Override
public ReactionToIncomingMessage processIncomingMessage(Object message) {
if (isIncomingMessageValid(message, CurrentDemandResType.class)) {
V2GMessage v2gMessageRes = (V2GMessage) message;
CurrentDemandResType currentDemandRes =
(CurrentDemandResType) v2gMessageRes.getBody().getBodyElement().getValue();
// ReceiptRequired has higher priority than a possible EVSENotification=Renegotiate
if (currentDemandRes.isReceiptRequired() != null && currentDemandRes.isReceiptRequired() && getCommSessionContext().isTlsConnection()) {
MeteringReceiptReqType meteringReceiptReq = new MeteringReceiptReqType();
/*
* Experience from the test symposium in San Diego (April 2016):
* The Id element of the signature is not restricted in size by the standard itself. But on embedded
* systems, the memory is very limited which is why we should not use long IDs for the signature reference
* element. A good size would be 3 characters max (like the example in the ISO 15118-2 annex J)
*/
meteringReceiptReq.setId("id1");
meteringReceiptReq.setMeterInfo(currentDemandRes.getMeterInfo());
meteringReceiptReq.setSAScheduleTupleID(currentDemandRes.getSAScheduleTupleID());
meteringReceiptReq.setSessionID(getCommSessionContext().getSessionID());
// Set xml reference element
getXMLSignatureRefElements().put(
meteringReceiptReq.getId(),
SecurityUtils.generateDigest(
meteringReceiptReq.getId(),
getMessageHandler().getJaxbElement(meteringReceiptReq)));
// Set signing private key
setSignaturePrivateKey(SecurityUtils.getPrivateKey(
SecurityUtils.getKeyStore(
GlobalValues.EVCC_KEYSTORE_FILEPATH.toString(),
GlobalValues.PASSPHRASE_FOR_CERTIFICATES_AND_KEYS.toString()),
GlobalValues.ALIAS_CONTRACT_CERTIFICATE.toString())
);
return getSendMessage(meteringReceiptReq, V2GMessages.METERING_RECEIPT_RES);
}
// TODO check for the other parameters in the currentDemandRes and react accordingly
DCEVSEStatusType dcEVSEStatus = currentDemandRes.getDCEVSEStatus();
switch ((EVSENotificationType) dcEVSEStatus.getEVSENotification()) {
case STOP_CHARGING:
getCommSessionContext().setChargingSession(ChargingSessionType.TERMINATE);
return getSendMessage(getPowerDeliveryReq(ChargeProgressType.STOP),
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)");
default:
// TODO regard [V2G2-305] (new SalesTariff if EAmount not yet met and tariff finished)
if (getCommSessionContext().getEvController().isChargingLoopActive()) {
// Check whether or not the EV controller triggered a renegotiation
if (getCommSessionContext().isRenegotiationRequested()) {
return getSendMessage(getPowerDeliveryReq(ChargeProgressType.RENEGOTIATE),
V2GMessages.POWER_DELIVERY_RES,
" (ChargeProgress = RE_NEGOTIATION)");
} else {
return getSendMessage(getCurrentDemandReq(), V2GMessages.CURRENT_DEMAND_RES);
}
} else {
/* 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)");
}
}
} else {
return new TerminateSession("Incoming message raised an error");
}
}
}

View File

@@ -0,0 +1,128 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.evcc.states;
import com.v2gclarity.risev2g.evcc.session.V2GCommunicationSessionEVCC;
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.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;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.MeteringReceiptResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.V2GMessage;
public class WaitForMeteringReceiptRes extends ClientState {
private boolean acCharging;
public WaitForMeteringReceiptRes(V2GCommunicationSessionEVCC commSessionContext) {
super(commSessionContext);
}
@Override
public ReactionToIncomingMessage processIncomingMessage(Object message) {
if (isIncomingMessageValid(message, MeteringReceiptResType.class)) {
V2GMessage v2gMessageRes = (V2GMessage) message;
MeteringReceiptResType meteringReceiptRes =
(MeteringReceiptResType) v2gMessageRes.getBody().getBodyElement().getValue();
EVSENotificationType evseNotification = null;
if (getCommSessionContext().getRequestedEnergyTransferMode().toString().startsWith("AC")) {
setAcCharging(true);
evseNotification = ((ACEVSEStatusType) meteringReceiptRes.getEVSEStatus().getValue()).getEVSENotification();
} else if (getCommSessionContext().getRequestedEnergyTransferMode().toString().startsWith("DC")) {
setAcCharging(false);
evseNotification = ((DCEVSEStatusType) meteringReceiptRes.getEVSEStatus().getValue()).getEVSENotification();
} else {
return new TerminateSession("RequestedEnergyTransferMode '" + getCommSessionContext().getRequestedEnergyTransferMode().toString() +
"is neither of type AC nor DC");
}
switch (evseNotification) {
case STOP_CHARGING:
getCommSessionContext().setChargingSession(ChargingSessionType.TERMINATE);
return getSendMessage(getPowerDeliveryReq(ChargeProgressType.STOP),
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)");
default:
// TODO regard [V2G2-305] (new SalesTariff if EAmount not yet met and tariff finished)
if (isAcCharging() && getCommSessionContext().getEvController().isChargingLoopActive()) {
// Check whether or not the EV controller triggered a renegotiation
if (getCommSessionContext().isRenegotiationRequested()) {
return getSendMessage(getPowerDeliveryReq(ChargeProgressType.RENEGOTIATE),
V2GMessages.POWER_DELIVERY_RES,
" (ChargeProgress = RE_NEGOTIATION)");
} else {
ChargingStatusReqType chargingStatusReq = new ChargingStatusReqType();
return getSendMessage(chargingStatusReq, V2GMessages.CHARGING_STATUS_RES);
}
} else if (getCommSessionContext().getEvController().isChargingLoopActive()) {
// Check whether or not the EV controller triggered a renegotiation
if (getCommSessionContext().isRenegotiationRequested()) {
return getSendMessage(getPowerDeliveryReq(ChargeProgressType.RENEGOTIATE),
V2GMessages.POWER_DELIVERY_RES,
" (ChargeProgress = RE_NEGOTIATION)");
} else {
return getSendMessage(getCurrentDemandReq(), V2GMessages.CURRENT_DEMAND_RES);
}
} else {
/* 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)");
}
}
} else {
return new TerminateSession("Incoming message raised an error");
}
}
private boolean isAcCharging() {
return acCharging;
}
private void setAcCharging(boolean acCharging) {
this.acCharging = acCharging;
}
}

View File

@@ -0,0 +1,84 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.evcc.states;
import com.v2gclarity.risev2g.evcc.session.V2GCommunicationSessionEVCC;
import com.v2gclarity.risev2g.shared.enumerations.GlobalValues;
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.SecurityUtils;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.AuthorizationReqType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PaymentDetailsResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.V2GMessage;
public class WaitForPaymentDetailsRes extends ClientState {
public WaitForPaymentDetailsRes(V2GCommunicationSessionEVCC commSessionContext) {
super(commSessionContext);
}
@Override
public ReactionToIncomingMessage processIncomingMessage(Object message) {
if (isIncomingMessageValid(message, PaymentDetailsResType.class)) {
V2GMessage v2gMessageRes = (V2GMessage) message;
PaymentDetailsResType paymentDetailsRes =
(PaymentDetailsResType) v2gMessageRes.getBody().getBodyElement().getValue();
/*
* A reaction on the response code OK_CERTIFICATE_EXPIRES_SOON is not needed as this check
* is already done by EVCC itself before deciding to send CertificateUpdateReq/CertificateInstallationReq
*/
if (paymentDetailsRes.getGenChallenge() == null) {
return new TerminateSession("GenChallenge not provided in PaymentDetailsRes");
} else {
// Save the sent genChallenge for the state WaitForAuthorizationRes if EVSEProcessing is set to ONGOING
getCommSessionContext().setSentGenChallenge(paymentDetailsRes.getGenChallenge());
AuthorizationReqType authorizationReq = getAuthorizationReq(paymentDetailsRes.getGenChallenge());
// Set xml reference element
getXMLSignatureRefElements().put(
authorizationReq.getId(),
SecurityUtils.generateDigest(
authorizationReq.getId(),
getMessageHandler().getJaxbElement(authorizationReq)));
// Set signing private key
setSignaturePrivateKey(SecurityUtils.getPrivateKey(
SecurityUtils.getKeyStore(
GlobalValues.EVCC_KEYSTORE_FILEPATH.toString(),
GlobalValues.PASSPHRASE_FOR_CERTIFICATES_AND_KEYS.toString()),
GlobalValues.ALIAS_CONTRACT_CERTIFICATE.toString())
);
return getSendMessage(authorizationReq, V2GMessages.AUTHORIZATION_RES);
}
} else {
return new TerminateSession("Incoming message raised an error");
}
}
}

View File

@@ -0,0 +1,161 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.evcc.states;
import java.security.KeyStore;
import com.v2gclarity.risev2g.evcc.session.V2GCommunicationSessionEVCC;
import com.v2gclarity.risev2g.shared.enumerations.GlobalValues;
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.SecurityUtils;
import com.v2gclarity.risev2g.shared.utils.SecurityUtils.ContractCertificateStatus;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.CertificateInstallationReqType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.CertificateUpdateReqType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PaymentOptionType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PaymentServiceSelectionResType;
public class WaitForPaymentServiceSelectionRes extends ClientState {
public WaitForPaymentServiceSelectionRes(V2GCommunicationSessionEVCC commSessionContext) {
super(commSessionContext);
}
@Override
public ReactionToIncomingMessage processIncomingMessage(Object message) {
if (isIncomingMessageValid(message, PaymentServiceSelectionResType.class)) {
if (getCommSessionContext().getSelectedPaymentOption().equals(PaymentOptionType.CONTRACT)) {
if (getCommSessionContext().getContractCertStatus().equals(ContractCertificateStatus.UNKNOWN)) {
getCommSessionContext().setContractCertStatus(SecurityUtils.getContractCertificateStatus());
}
// 1. Check if certificate installation is needed
if (getCommSessionContext().getContractCertStatus().equals(ContractCertificateStatus.INSTALLATION_NEEDED)) {
if (getCommSessionContext().isCertificateServiceAvailable((short) 1)) {
getLogger().info("Trying to install new contract certificate");
return getSendMessage(getCertificateInstallationReq(), V2GMessages.CERTIFICATE_INSTALLATION_RES);
} else return new TerminateSession("Certificate installation needed but service is not available");
}
// 2. Check if certificate update is needed (means: certificate is available but expires soon)
if (getCommSessionContext().getContractCertStatus().equals(ContractCertificateStatus.UPDATE_NEEDED)) {
if (getCommSessionContext().isCertificateServiceAvailable((short) 2)) {
getLogger().info("Trying to update contract certificate");
return getSendMessage(getCertificateUpdateReq(), V2GMessages.CERTIFICATE_UPDATE_RES);
} else return new TerminateSession("Certificate update needed but service is not available");
}
return getSendMessage(getPaymentDetailsReq(), V2GMessages.PAYMENT_DETAILS_RES);
} else if (getCommSessionContext().getSelectedPaymentOption().equals(PaymentOptionType.EXTERNAL_PAYMENT)) {
return getSendMessage(getAuthorizationReq(null), V2GMessages.AUTHORIZATION_RES);
} else {
return new TerminateSession("No valid PaymentOptionType available");
}
} else {
return new TerminateSession("Incoming message raised an error");
}
}
private CertificateInstallationReqType getCertificateInstallationReq() {
KeyStore evccKeyStore = SecurityUtils.getKeyStore(
GlobalValues.EVCC_KEYSTORE_FILEPATH.toString(),
GlobalValues.PASSPHRASE_FOR_CERTIFICATES_AND_KEYS.toString());
CertificateInstallationReqType certInstallationReq = new CertificateInstallationReqType();
/*
* Experience from the test symposium in San Diego (April 2016):
* The Id element of the signature is not restricted in size by the standard itself. But on embedded
* systems, the memory is very limited which is why we should not use long IDs for the signature reference
* element. A good size would be 3 characters max (like the example in the ISO 15118-2 annex J)
*/
certInstallationReq.setId("ID1");
certInstallationReq.setListOfRootCertificateIDs(
SecurityUtils.getListOfRootCertificateIDs(
GlobalValues.EVCC_TRUSTSTORE_FILEPATH.toString(),
GlobalValues.PASSPHRASE_FOR_CERTIFICATES_AND_KEYS.toString()));
certInstallationReq.setOEMProvisioningCert(
SecurityUtils.getCertificateChain(
evccKeyStore, GlobalValues.ALIAS_OEM_PROV_CERTIFICATE.toString()).getCertificate());
// Set xml reference element
getXMLSignatureRefElements().put(
certInstallationReq.getId(),
SecurityUtils.generateDigest(
certInstallationReq.getId(),
getMessageHandler().getJaxbElement(certInstallationReq)));
// Set signing private key
setSignaturePrivateKey(SecurityUtils.getPrivateKey(
SecurityUtils.getKeyStore(
GlobalValues.EVCC_KEYSTORE_FILEPATH.toString(),
GlobalValues.PASSPHRASE_FOR_CERTIFICATES_AND_KEYS.toString()),
GlobalValues.ALIAS_OEM_PROV_CERTIFICATE.toString())
);
return certInstallationReq;
}
private CertificateUpdateReqType getCertificateUpdateReq() {
CertificateUpdateReqType certificateUpdateReq = new CertificateUpdateReqType();
certificateUpdateReq.setContractSignatureCertChain(
SecurityUtils.getCertificateChain(
SecurityUtils.getKeyStore(
GlobalValues.EVCC_KEYSTORE_FILEPATH.toString(),
GlobalValues.PASSPHRASE_FOR_CERTIFICATES_AND_KEYS.toString()),
GlobalValues.ALIAS_CONTRACT_CERTIFICATE.toString()));
certificateUpdateReq.setEMAID(SecurityUtils.getEMAID(GlobalValues.PASSPHRASE_FOR_CERTIFICATES_AND_KEYS.toString()).getValue());
/*
* Experience from the test symposium in San Diego (April 2016):
* The Id element of the signature is not restricted in size by the standard itself. But on embedded
* systems, the memory is very limited which is why we should not use long IDs for the signature reference
* element. A good size would be 3 characters max (like the example in the ISO 15118-2 annex J)
*/
certificateUpdateReq.setId("ID1");
certificateUpdateReq.setListOfRootCertificateIDs(
SecurityUtils.getListOfRootCertificateIDs(
GlobalValues.EVCC_TRUSTSTORE_FILEPATH.toString(),
GlobalValues.PASSPHRASE_FOR_CERTIFICATES_AND_KEYS.toString()));
// Set xml reference element
getXMLSignatureRefElements().put(
certificateUpdateReq.getId(),
SecurityUtils.generateDigest(
certificateUpdateReq.getId(),
getMessageHandler().getJaxbElement(certificateUpdateReq)));
// Set signing private key
setSignaturePrivateKey(SecurityUtils.getPrivateKey(
SecurityUtils.getKeyStore(
GlobalValues.EVCC_KEYSTORE_FILEPATH.toString(),
GlobalValues.PASSPHRASE_FOR_CERTIFICATES_AND_KEYS.toString()),
GlobalValues.ALIAS_CONTRACT_CERTIFICATE.toString())
);
return certificateUpdateReq;
}
}

View File

@@ -0,0 +1,118 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.evcc.states;
import com.v2gclarity.risev2g.evcc.evController.IDCEVController;
import com.v2gclarity.risev2g.evcc.session.V2GCommunicationSessionEVCC;
import com.v2gclarity.risev2g.shared.enumerations.CPStates;
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.v2gMessages.msgDef.ChargeParameterDiscoveryReqType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargingSessionType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargingStatusReqType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.DCEVStatusType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PowerDeliveryResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.V2GMessage;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.WeldingDetectionReqType;
public class WaitForPowerDeliveryRes extends ClientState {
public WaitForPowerDeliveryRes(V2GCommunicationSessionEVCC commSessionContext) {
super(commSessionContext);
}
@Override
public ReactionToIncomingMessage processIncomingMessage(Object message) {
if (isIncomingMessageValid(message, PowerDeliveryResType.class)) {
PowerDeliveryResType powerDeliveryRes =
(PowerDeliveryResType) ((V2GMessage) message).getBody().getBodyElement().getValue();
/*
* TODO clarification needed on how to react on EVSENotification
*
* EVSENotification=RE_NEGOATION is ignored, because an SECC triggered renegotiation is only
* to be reacted on in the messages
* - ChargingStatusRes
* - MeteringReceiptRes
* - CurrentDemandRes
*
* But how to react on EVSENotification=STOP?
*/
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);
}
ChargeParameterDiscoveryReqType chargeParameterDiscoveryReq = getChargeParameterDiscoveryReq();
/*
* Save this request in case the ChargeParameterDiscoveryRes indicates that the EVSE is
* still processing. Then this request can just be resent instead of asking the EV again.
*/
getCommSessionContext().setChargeParameterDiscoveryReq(chargeParameterDiscoveryReq);
return getSendMessage(chargeParameterDiscoveryReq, V2GMessages.CHARGE_PARAMETER_DISCOVERY_RES);
} 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();
return getSendMessage(chargingStatusReq, V2GMessages.CHARGING_STATUS_RES);
} else {
return getSendMessage(getCurrentDemandReq(), V2GMessages.CURRENT_DEMAND_RES);
}
}
} else {
return new TerminateSession("Incoming message raised an error");
}
}
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)) {
WeldingDetectionReqType weldingDetectionReq = new WeldingDetectionReqType();
DCEVStatusType dcEVStatus = ((IDCEVController) getCommSessionContext().getEvController()).getDCEVStatus();
weldingDetectionReq.setDCEVStatus(dcEVStatus);
return getSendMessage(weldingDetectionReq, V2GMessages.WELDING_DETECTION_RES);
} else {
return new TerminateSession("CP state B not ready (current state = " +
getCommSessionContext().getEvController().getCPState() +
")");
}
} else {
return getSendMessage(getSessionStopReq(chargingSessionType),
V2GMessages.SESSION_STOP_RES, "(ChargingSession = " +
chargingSessionType.toString() + ")");
}
}
}

View File

@@ -0,0 +1,87 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.evcc.states;
import java.util.concurrent.TimeUnit;
import com.v2gclarity.risev2g.evcc.evController.IDCEVController;
import com.v2gclarity.risev2g.evcc.session.V2GCommunicationSessionEVCC;
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.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;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.V2GMessage;
public class WaitForPreChargeRes extends ClientState {
public WaitForPreChargeRes(V2GCommunicationSessionEVCC commSessionContext) {
super(commSessionContext);
}
@Override
public ReactionToIncomingMessage processIncomingMessage(Object message) {
if (isIncomingMessageValid(message, PreChargeResType.class)) {
V2GMessage v2gMessageRes = (V2GMessage) message;
PreChargeResType preChargeRes =
(PreChargeResType) v2gMessageRes.getBody().getBodyElement().getValue();
// TODO how to react to DC_EVSEStatus
IDCEVController dcEvController = (IDCEVController) getCommSessionContext().getEvController();
double targetVoltage = dcEvController.getTargetVoltage().getValue() * Math.pow(10, dcEvController.getTargetVoltage().getMultiplier());
double presentVoltage = preChargeRes.getEVSEPresentVoltage().getValue() * Math.pow(10, preChargeRes.getEVSEPresentVoltage().getMultiplier());
// 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);
return getSendMessage(getPowerDeliveryReq(ChargeProgressType.START), V2GMessages.POWER_DELIVERY_RES);
} else {
long elapsedTime = System.nanoTime() - getCommSessionContext().getOngoingTimer();
long elapsedTimeInMs = TimeUnit.MILLISECONDS.convert(elapsedTime, TimeUnit.NANOSECONDS);
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());
preChargeReq.setEVTargetVoltage(dcEvController.getTargetVoltage());
return getSendMessage(preChargeReq, V2GMessages.PRE_CHARGE_RES, Math.min((TimeRestrictions.V2G_EVCC_PRE_CHARGE_TIMEOUT - (int) elapsedTimeInMs), TimeRestrictions.getV2gEvccMsgTimeout(V2GMessages.PRE_CHARGE_RES)));
}
}
} else {
return new TerminateSession("Incoming message raised an error");
}
}
}

View File

@@ -0,0 +1,61 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.evcc.states;
import com.v2gclarity.risev2g.evcc.session.V2GCommunicationSessionEVCC;
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.v2gMessages.msgDef.ServiceDetailReqType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ServiceDetailResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.V2GMessage;
public class WaitForServiceDetailRes extends ClientState {
public WaitForServiceDetailRes(V2GCommunicationSessionEVCC commSessionContext) {
super(commSessionContext);
}
@Override
public ReactionToIncomingMessage processIncomingMessage(Object message) {
if (isIncomingMessageValid(message, ServiceDetailResType.class)) {
ServiceDetailResType serviceDetailRes =
(ServiceDetailResType) ((V2GMessage) message).getBody().getBodyElement().getValue();
/*
* Currently there seems to be no need to check the service details, the parameterSets are clearly
* defined in Table 105 of ISO/IEC 15118. If no negative response code was received for the
* requested details of a serviceID, then the EVCC as well as SECC should offer the same parameterSets
*/
ServiceDetailReqType serviceDetailReq = getServiceDetailReq();
if (serviceDetailReq != null)
return getSendMessage(serviceDetailReq, V2GMessages.SERVICE_DETAIL_RES);
else
return getSendMessage(getPaymentServiceSelectionReq(), V2GMessages.PAYMENT_SERVICE_SELECTION_RES);
} else {
return new TerminateSession("Incoming message raised an error");
}
}
}

View File

@@ -0,0 +1,196 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.evcc.states;
import com.v2gclarity.risev2g.evcc.session.V2GCommunicationSessionEVCC;
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;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ServiceDiscoveryResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ServiceListType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ServiceType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.V2GMessage;
public class WaitForServiceDiscoveryRes extends ClientState {
public WaitForServiceDiscoveryRes(V2GCommunicationSessionEVCC commSessionContext) {
super(commSessionContext);
}
@Override
public ReactionToIncomingMessage processIncomingMessage(Object message) {
if (isIncomingMessageValid(message, ServiceDiscoveryResType.class)) {
ServiceDiscoveryResType serviceDiscoveryRes =
(ServiceDiscoveryResType) ((V2GMessage) message).getBody().getBodyElement().getValue();
getCommSessionContext().getSelectedServices().getSelectedService().clear(); // just to be sure
/*
* For every service whose details need to be requested, a ServiceDetailReq message must be sent.
* First by WaitForServiceDiscoveryRes, then by WaitForServiceDetailRes. In order to keep track of
* which service details still need to be requested, we use this helper list.
*/
getCommSessionContext().getServiceDetailsToBeRequested().clear(); // just to be sure
// 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) {
// Check if requested energy transfer mode is supported
EnergyTransferModeType requestedEnergyTransferMode = getRequestedEnergyTransferMode();
if (serviceDiscoveryRes.getChargeService().getSupportedEnergyTransferMode()
.getEnergyTransferMode().contains(requestedEnergyTransferMode)) {
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, which is " + requestedEnergyTransferMode.toString());
}
} else return new TerminateSession("No charge service available");
getCommSessionContext().setSelectedPaymentOption(getSelectedPaymentOption(serviceDiscoveryRes.getPaymentOptionList()));
// Check for the usage of value added services (VAS)
if (useVAS(serviceDiscoveryRes)) {
return getSendMessage(getServiceDetailReq(), V2GMessages.SERVICE_DETAIL_RES);
} else {
return getSendMessage(getPaymentServiceSelectionReq(), V2GMessages.PAYMENT_SERVICE_SELECTION_RES);
}
} else {
return new TerminateSession("Incoming message raised an error");
}
}
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.
* Furthermore, it must be checked if VAS are allowed (-> only if TLS connection is used)
*/
private boolean useVAS(ServiceDiscoveryResType serviceDiscoveryRes) {
if (serviceDiscoveryRes.getServiceList() != null &&
getCommSessionContext().getTransportLayerClient() instanceof TLSClient) {
getLogger().debug("List of offered value added services: ");
for (ServiceType service : serviceDiscoveryRes.getServiceList().getService()) {
getLogger().debug("ID = " + service.getServiceID() + ", name = " + service.getServiceName());
}
// Check if certificate service is needed
if (isCertificateServiceOffered(serviceDiscoveryRes.getServiceList())) {
getCommSessionContext().setContractCertStatus(SecurityUtils.getContractCertificateStatus());
if (getCommSessionContext().getContractCertStatus().equals(ContractCertificateStatus.INSTALLATION_NEEDED))
addSelectedService(2, (short) 1);
else if (getCommSessionContext().getContractCertStatus().equals(ContractCertificateStatus.UPDATE_NEEDED))
addSelectedService(2, (short) 2);
}
// Optionally, other value added services can be checked for here ...
} else return false;
return (getCommSessionContext().getServiceDetailsToBeRequested().size() > 0) ? true : false;
}
private void addSelectedService(int serviceID, Short parameterSetID) {
/*
* The SelectedServiceType holds an optional parameter for parameterSetID. This parameterSetID
* will be retrieved later by the ServiceDetailRes. However, in case of certificate installation/update
* a check for the needed parameterSetID is already done at this state (and the values are defined
* by Table 105 in ISO/IEC 15118) which is why we already save the parameterSetID for certificate here.
*/
SelectedServiceType selectedService = new SelectedServiceType();
selectedService.setServiceID(serviceID);
selectedService.setParameterSetID(parameterSetID);
getCommSessionContext().getSelectedServices().getSelectedService().add(selectedService);
/*
* There are no service details (a ServiceParameterList) yet defined for a charge service,
* so don't send a ServiceDetailReq for a ChargeService
*/
if (serviceID != 1)
getCommSessionContext().getServiceDetailsToBeRequested().add(serviceID);
}
private boolean isCertificateServiceOffered(ServiceListType offeredServiceList) {
if (offeredServiceList == null) {
getLogger().debug("No value added services offered by EVCC");
return false;
}
for (ServiceType service : offeredServiceList.getService()) {
if (service.getServiceCategory().equals(ServiceCategoryType.CONTRACT_CERTIFICATE))
return true;
}
return false;
}
}

View File

@@ -0,0 +1,89 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.evcc.states;
import com.v2gclarity.risev2g.evcc.session.V2GCommunicationSessionEVCC;
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.ByteUtils;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ResponseCodeType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ServiceDiscoveryReqType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.SessionSetupResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.V2GMessage;
public class WaitForSessionSetupRes extends ClientState {
public WaitForSessionSetupRes(V2GCommunicationSessionEVCC commSessionContext) {
super(commSessionContext);
}
@Override
public ReactionToIncomingMessage processIncomingMessage(Object message) {
if (isIncomingMessageValid(message, SessionSetupResType.class)) {
byte[] receivedSessionID = ((V2GMessage) message).getHeader().getSessionID();
SessionSetupResType sessionSetupRes =
(SessionSetupResType) ((V2GMessage) message).getBody().getBodyElement().getValue();
if (sessionSetupRes.getResponseCode().equals(ResponseCodeType.OK_NEW_SESSION_ESTABLISHED)) {
getLogger().debug("Negotiated session ID is " + ByteUtils.toHexString(receivedSessionID));
getCommSessionContext().setOldSessionJoined(false);
} else if (sessionSetupRes.getResponseCode().equals(ResponseCodeType.OK_OLD_SESSION_JOINED)) {
getLogger().debug("Previous charging session joined (session ID = " + ByteUtils.toHexString(receivedSessionID) + ")");
/*
* Mark that the old session was joined in order to resend
* - SelectedPaymentOption and
* - RequestedEnergyTransferMode
* according to 8.4.2. Those values should be persisted in the properties file.
*/
getCommSessionContext().setOldSessionJoined(true);
} else {
getCommSessionContext().setOldSessionJoined(false);
getLogger().error("No negative response code received, but positive response code '" +
sessionSetupRes.getResponseCode().toString() + "' is " +
"neither OK_NEW_SESSION_ESTABLISHED nor OK_OLD_SESSION_JOINED");
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();
/*
* If it is desired to restrict the services to a certain scope and/or category (optional),
* then this is the place to do it.
*/
return getSendMessage(serviceDiscoveryReq, V2GMessages.SERVICE_DISCOVERY_RES);
} else {
getCommSessionContext().setOldSessionJoined(false);
return new TerminateSession("Incoming message raised an error");
}
}
}

View File

@@ -0,0 +1,51 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
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 {
public WaitForSessionStopRes(V2GCommunicationSessionEVCC commSessionContext) {
super(commSessionContext);
}
@Override
public ReactionToIncomingMessage processIncomingMessage(Object message) {
if (isIncomingMessageValid(message, SessionStopResType.class)) {
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

@@ -0,0 +1,112 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.evcc.states;
import java.util.concurrent.TimeUnit;
import com.v2gclarity.risev2g.evcc.session.V2GCommunicationSessionEVCC;
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;
import com.v2gclarity.risev2g.shared.v2gMessages.appProtocol.SupportedAppProtocolRes;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.SessionSetupReqType;
public class WaitForSupportedAppProtocolRes extends ClientState {
public WaitForSupportedAppProtocolRes(V2GCommunicationSessionEVCC commSessionContext) {
super(commSessionContext);
}
@Override
public ReactionToIncomingMessage processIncomingMessage(Object message) {
if (message instanceof SupportedAppProtocolRes) {
getLogger().debug("SupportedAppProtocolRes received");
SupportedAppProtocolRes supportedAppProtocolRes = (SupportedAppProtocolRes) message;
SessionSetupReqType sessionSetupReq = new SessionSetupReqType();
boolean match = false;
if (supportedAppProtocolRes.getResponseCode().equals(ResponseCodeType.OK_SUCCESSFUL_NEGOTIATION) ||
supportedAppProtocolRes.getResponseCode().equals(ResponseCodeType.OK_SUCCESSFUL_NEGOTIATION_WITH_MINOR_DEVIATION)) {
// Check which schemaID is to be chosen
for (AppProtocolType evccAppProtocol : getCommSessionContext().getSupportedAppProtocols()) {
if (evccAppProtocol.getSchemaID() == supportedAppProtocolRes.getSchemaID()) {
/*
* If the EVCC supports more than one appProtocol or even minor deviations,
* then the EVCC must in some way be able to react accordingly to those different
* versions. Currently, only IS version of April 2014 is supported (see [V2G2-098]).
*/
getCommSessionContext().setChosenAppProtocol(evccAppProtocol);
match = true;
break;
}
}
} else {
return new TerminateSession("No supported appProtocol found (negative response code)");
}
// Double check if - despite an OK_ response code - a valid schemaID has been sent
if (match) {
sessionSetupReq.setEVCCID(MiscUtils.getMacAddress());
/*
* 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 15118 controller is shut down for energy
* saving reasons.
* The initial value for a completely new charging session must be 00.
*/
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: " +
supportedAppProtocolRes.getSchemaID());
}
long elapsedTime = System.nanoTime() - getCommSessionContext().getV2gEVCCCommunicationSetupTimer();
long elapsedTimeInMs = TimeUnit.MILLISECONDS.convert(elapsedTime, TimeUnit.NANOSECONDS);
return getSendMessage(sessionSetupReq, V2GMessages.SESSION_SETUP_RES, (int) Math.min(
TimeRestrictions.getV2gEvccMsgTimeout(V2GMessages.SESSION_SETUP_RES),
TimeRestrictions.V2G_EVCC_COMMUNICATION_SETUP_TIMEOUT - elapsedTimeInMs
));
} else {
String className = (message != null) ? message.getClass().getSimpleName() : "<unkown message type>";
return new TerminateSession("Invalid message (" + className + ") at this state (" + this.getClass().getSimpleName() + ")");
}
}
}

View File

@@ -0,0 +1,69 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.evcc.states;
import com.v2gclarity.risev2g.evcc.session.V2GCommunicationSessionEVCC;
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.v2gMessages.msgDef.ChargingSessionType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.V2GMessage;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.WeldingDetectionResType;
public class WaitForWeldingDetectionRes extends ClientState {
public WaitForWeldingDetectionRes(V2GCommunicationSessionEVCC commSessionContext) {
super(commSessionContext);
}
@Override
public ReactionToIncomingMessage processIncomingMessage(Object message) {
if (isIncomingMessageValid(message, WeldingDetectionResType.class)) {
WeldingDetectionResType weldingDetectionRes =
(WeldingDetectionResType) ((V2GMessage) message).getBody().getBodyElement().getValue();
/*
* [V2G2-620] states that the EVCC shall send a WeldingDetectionReq again if the
* Welding Detection function has not finished on EV side. But how is this checked?
*
* How to react on DCEVSEStatus values?
*/
if (getCommSessionContext().getChargingSession() != null &&
getCommSessionContext().getChargingSession() == ChargingSessionType.PAUSE) {
return getSendMessage(getSessionStopReq(ChargingSessionType.PAUSE),
V2GMessages.SESSION_STOP_RES, "(ChargingSession = " +
ChargingSessionType.PAUSE.toString() + ")");
} else {
return getSendMessage(getSessionStopReq(ChargingSessionType.TERMINATE),
V2GMessages.SESSION_STOP_RES, "(ChargingSession = " +
ChargingSessionType.TERMINATE.toString() + ")");
}
} else {
return new TerminateSession("Incoming message raised an error");
}
}
}

View File

@@ -0,0 +1,251 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.evcc.transportLayer;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Inet6Address;
import java.util.Arrays;
import java.util.Observable;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.v2gclarity.risev2g.shared.enumerations.V2GMessages;
import com.v2gclarity.risev2g.shared.messageHandling.TerminateSession;
import com.v2gclarity.risev2g.shared.misc.TimeRestrictions;
import com.v2gclarity.risev2g.shared.misc.V2GTPMessage;
import com.v2gclarity.risev2g.shared.utils.ByteUtils;
import com.v2gclarity.risev2g.shared.utils.MiscUtils;
public abstract class StatefulTransportLayerClient extends Observable implements Runnable {
private Logger logger = LogManager.getLogger(this.getClass().getSimpleName());
private byte[] v2gTPHeader;
private byte[] v2gTPPayload;
private byte[] v2gTPMessage;
private InputStream inStream;
private OutputStream outStream;
private int payloadLength;
private int bytesReadFromInputStream;
private Inet6Address clientAddress;
private int clientPort;
private int timeout;
private boolean stopAlreadyInitiated;
protected void initialize() {
getLogger().debug("Initializing client connection ...");
setClientPort(MiscUtils.getRandomPortNumber());
setClientAddress(MiscUtils.getLinkLocalAddress());
setV2gTPHeader(new byte[8]);
setTimeout(TimeRestrictions.getV2gEvccMsgTimeout(V2GMessages.SUPPORTED_APP_PROTOCOL_RES)); // Needed for the supportedAppProtocol timeout
}
protected boolean processIncomingMessage() throws IOException {
/*
* Read header (8 bytes) of incoming V2GTPMessage to further allocate a byte array with
* the appropriate length.
*/
try {
setBytesReadFromInputStream(getInStream().read(getV2gTPHeader()));
} catch (IOException e) {
/*
* If there are no bytes buffered on the socket, or all buffered bytes have been consumed by read,
* then all subsequent calls to read will throw an IOException.
*/
stopAndNotify("IOExeption occurred while trying to read the header of the incoming message. "
+ "Maybe timeout occurred?", e);
return false;
} catch (NullPointerException e2) {
stopAndNotify("NullPointerException occurred while trying to read the header of the incoming message", e2);
return false;
}
if (getBytesReadFromInputStream() < 0) {
stopAndNotify("No bytes read from input stream, server socket seems to be closed", null);
return false;
}
/*
* The payload length is written to the last 4 bytes (v2gTPHeader[4] to v2gTPHeader[7])
* of the V2GTP header. The biggest ISO 15118 message in size is the CertificateInstallationRes, which in EXI
* consumes usually 3.000 to 4.000 bytes max. Let's use a threshold of 10.000 just to be safe to
* check for unreasonably high payload lengths.
*
* Change this value if you use a V2GTP payload type for a proprietary, manufacturer-specific use (see Table 10 of ISO 15118-2)
* that requires bigger payloads!
*/
setPayloadLength(ByteUtils.toIntFromByteArray(Arrays.copyOfRange(getV2gTPHeader(), 4, 8)));
if (getPayloadLength() > 10000) {
stopAndNotify("Payload length of V2GTP message is inappropiately high (" + getPayloadLength() + " bytes)! " +
"There must be an error in the V2GTP message header!", null);
return false;
} else {
getLogger().debug("Length of V2GTP payload in bytes according to V2GTP header: " + getPayloadLength());
setV2gTPPayload(new byte[getPayloadLength()]);
getInStream().read(getV2gTPPayload());
getLogger().debug("Message received");
setV2gTPMessage(new byte[getV2gTPHeader().length + getV2gTPPayload().length]);
System.arraycopy(getV2gTPHeader(), 0, getV2gTPMessage(), 0, getV2gTPHeader().length);
System.arraycopy(getV2gTPPayload(), 0, getV2gTPMessage(), getV2gTPHeader().length, getV2gTPPayload().length);
}
// Block another while-run before the new Socket timeout has been provided by send()
// TODO is there a more elegant way of blocking (this is rather resource-consuming)?
setTimeout(-1);
setChanged();
notifyObservers(getV2gTPMessage());
return true;
}
public abstract void send(V2GTPMessage message, int timeout);
/**
* If an error occurred in the run()-method, the TCP client will be stopped by closing all streams
* and the socket and interrupting the Thread. V2GCommunicationSessionEVCC will be notified as well.
* The method's statements will not be executed if a stop of the TCP client has already been
* initiated by the V2GCommunicationSessionEVCC (which might induce an error in the run()-method).
*
* @param errorMessage An error message explaining the reason for the error
* @param e An optional exception
*/
protected void stopAndNotify(String errorMessage, Exception e) {
if (!isStopAlreadyInitiated()) {
getLogger().error(errorMessage, e);
stop();
setStopAlreadyInitiated(true);
// Notify V2GCommunicationSessionEVCC about termination of session
setChanged();
notifyObservers(new TerminateSession(errorMessage));
}
}
public abstract void stop();
public Logger getLogger() {
return logger;
}
public void setLogger(Logger logger) {
this.logger = logger;
}
public byte[] getV2gTPHeader() {
return v2gTPHeader;
}
public void setV2gTPHeader(byte[] v2gTPHeader) {
this.v2gTPHeader = v2gTPHeader;
}
public byte[] getV2gTPPayload() {
return v2gTPPayload;
}
public void setV2gTPPayload(byte[] v2gTPPayload) {
this.v2gTPPayload = v2gTPPayload;
}
public byte[] getV2gTPMessage() {
return v2gTPMessage;
}
public void setV2gTPMessage(byte[] v2gTPMessage) {
this.v2gTPMessage = v2gTPMessage;
}
public InputStream getInStream() {
return inStream;
}
public void setInStream(InputStream inStream) {
this.inStream = inStream;
}
public OutputStream getOutStream() {
return outStream;
}
public void setOutStream(OutputStream outStream) {
this.outStream = outStream;
}
public int getPayloadLength() {
return payloadLength;
}
public void setPayloadLength(int payloadLength) {
this.payloadLength = payloadLength;
}
public int getBytesReadFromInputStream() {
return bytesReadFromInputStream;
}
public void setBytesReadFromInputStream(int bytesReadFromInputStream) {
this.bytesReadFromInputStream = bytesReadFromInputStream;
}
public Inet6Address getClientAddress() {
return clientAddress;
}
public void setClientAddress(Inet6Address clientAddress) {
this.clientAddress = clientAddress;
}
public int getClientPort() {
return clientPort;
}
public void setClientPort(int clientPort) {
this.clientPort = clientPort;
}
public int getTimeout() {
return timeout;
}
public void setTimeout(int timeout) {
this.timeout = timeout;
}
public boolean isStopAlreadyInitiated() {
return stopAlreadyInitiated;
}
public void setStopAlreadyInitiated(boolean stopAlreadyInitiated) {
this.stopAlreadyInitiated = stopAlreadyInitiated;
}
}

View File

@@ -0,0 +1,168 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.evcc.transportLayer;
import java.io.IOException;
import java.net.Inet6Address;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import com.v2gclarity.risev2g.shared.misc.V2GTPMessage;
public class TCPClient extends StatefulTransportLayerClient {
/*
* Lazy instantiation of the Singleton since a TCP connection might not be
* initialized if the SECCDiscovery message exchange failed.
* The volatile keyword ensures that multiple threads handle the uniqueTCPClientInstance
* variable correctly when it is being initialized to the TCPClient instance.
*/
private static volatile TCPClient uniqueTCPClientInstance;
private Socket tcpSocketToServer;
public TCPClient() {}
/**
* Checks for an instance and creates one if there isn't one already.
* The synchronized block is only entered once as long as there is no existing instance of the
* TCPClient (safes valuable resource).
* @return
*/
public static TCPClient getInstance() {
if (uniqueTCPClientInstance == null) {
synchronized (TCPClient.class) {
if (uniqueTCPClientInstance == null) {
uniqueTCPClientInstance = new TCPClient();
}
}
}
return uniqueTCPClientInstance;
}
/**
* Initializes the TCP client as soon as a SECCDiscoveryRes message arrived.
*
* @param host The address of the SECC's TCP server to connect to
* @param port The port of the SECC's TCP server to connect to
*/
public boolean initialize(Inet6Address host, int port) {
super.initialize();
try {
setTcpSocketToServer(new Socket(host, port));
setInStream(getTcpSocketToServer().getInputStream());
setOutStream(getTcpSocketToServer().getOutputStream());
getLogger().info("TCP client connection established \n\t from link-local address " +
getClientAddress() + " and port " + getClientPort() +
"\n\t to host " + host.getHostAddress() + " and port " + port);
return true;
} catch (UnknownHostException e) {
getLogger().error("TCP client connection failed (UnknownHostException)!", e);
} catch (IOException e) {
getLogger().error("TCP client connection failed (IOException)!", e);
}
return false;
}
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
if (getTimeout() > 0) {
try {
getSocketToServer().setSoTimeout(getTimeout());
if (!processIncomingMessage()) break;
} catch (SocketTimeoutException e) {
stopAndNotify("A timeout occurred while waiting for response message", null);
break;
} catch (IOException e2) {
stopAndNotify("An IOException occurred while trying to read message", e2);
break;
}
}
}
stop();
}
@Override
public void send(V2GTPMessage message, int timeout) {
setV2gTPMessage(null);
try {
getOutStream().write(message.getMessage());
getOutStream().flush();
getLogger().debug("Message sent");
setTimeout(timeout);
} catch (IOException e) {
getLogger().error("An undefined IOException occurred while trying to send message", e);
}
}
@Override
public void stop() {
if (!isStopAlreadyInitiated()) {
getLogger().debug("Stopping TCP client ...");
setStopAlreadyInitiated(true);
try {
getInStream().close();
getOutStream().close();
getTcpSocketToServer().close();
uniqueTCPClientInstance = null;
Thread.currentThread().interrupt();
} catch (IOException e) {
getLogger().error("Error occurred while trying to close TCP socket to server", e);
}
getLogger().debug("TCP client stopped");
}
}
public Socket getTcpSocketToServer() {
return tcpSocketToServer;
}
public void setTcpSocketToServer(Socket tcpSocketToServer) {
this.tcpSocketToServer = tcpSocketToServer;
}
public Socket getSocketToServer() {
return tcpSocketToServer;
}
public void setSocketToServer(Socket socketToServer) {
this.tcpSocketToServer = socketToServer;
}
}

View File

@@ -0,0 +1,242 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.evcc.transportLayer;
import java.io.IOException;
import java.net.Inet6Address;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import com.v2gclarity.risev2g.shared.enumerations.GlobalValues;
import com.v2gclarity.risev2g.shared.misc.TimeRestrictions;
import com.v2gclarity.risev2g.shared.misc.V2GTPMessage;
import com.v2gclarity.risev2g.shared.utils.SecurityUtils;
public class TLSClient extends StatefulTransportLayerClient {
/*
* Lazy instantiation of the Singleton since a TCP connection might not be
* initialized if the SECCDiscovery message exchange failed.
* The volatile keyword ensures that multiple threads handle the uniqueTCPClientInstance
* variable correctly when it is being initialized to the TCPClient instance.
*/
private static volatile TLSClient uniqueTLSClientInstance;
private SSLSocket tlsSocketToServer;
public TLSClient() {}
/**
* Checks for an instance and creates one if there isn't one already.
* The synchronized block is only entered once as long as there is no existing instance of the
* TLSClient (safes valuable resource).
* @return
*/
public static TLSClient getInstance() {
if (uniqueTLSClientInstance == null) {
synchronized (TLSClient.class) {
if (uniqueTLSClientInstance == null) {
uniqueTLSClientInstance = new TLSClient();
}
}
}
return uniqueTLSClientInstance;
}
/**
* Initializes the TLS client as soon as a SECCDiscoveryRes message arrived.
*
* @param host The address of the SECC's TLS server to connect to
* @param port The port of the SECC's TLS server to connect to
*/
public boolean initialize(Inet6Address host, int port) {
super.initialize();
try {
/*
* Setting the system property for the keystore and truststore via
* - System.setProperty("javax.net.ssl.keyStore", [filePath given as a String])
* - System.setProperty("javax.net.ssl.trustStore", [filePath given as a String])
* does not work in a JAR file since only getResourceAsStream works there (which on the other
* hand only returns an InputStream, not a file resource). Thus use setSSLFactories()
*/
SecurityUtils.setSSLContext(
GlobalValues.EVCC_KEYSTORE_FILEPATH.toString(),
GlobalValues.EVCC_TRUSTSTORE_FILEPATH.toString(),
GlobalValues.PASSPHRASE_FOR_CERTIFICATES_AND_KEYS.toString());
SSLSocketFactory sslSocketFactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
getLogger().debug("Creating socket to TLS server ...");
setTlsSocketToServer((SSLSocket) sslSocketFactory.createSocket(host, port));
getLogger().debug("TLS socket to server created");
setInStream(getTlsSocketToServer().getInputStream());
setOutStream(getTlsSocketToServer().getOutputStream());
/*
* The EVCC shall support at least one cipher suite as listed below according to
* the standard. An implementer may decide to choose only one of them:
* - TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256
* - TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256
*
* In ISO 15118-2, only the named elliptic curve "secp256r1" is allowed for ECDH(E). The jdk.tls.namedGroups property
* contains a comma-separated list within quotation marks of enabled named groups in preference order. The list of default
* named groups varies depending on what JDK release you are using. Set it on your Java command-line as follows:
*
* $ java -Djdk.tls.namedGroups="secp256r1"
*
* As it turns out, "secp256r1" is already the default first entry for Java 8 (and higher versions), but you should deactivate
* the other elliptic curves by reducing the list to this one entry only.
*/
String[] enabledCipherSuites = {
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256" // this cipher suite should be avoided, ECDH does not support perfect forward secrecy
};
getTlsSocketToServer().setEnabledCipherSuites(enabledCipherSuites);
// Set the supported TLS protocol
String[] enabledProtocols = {"TLSv1.2"};
getTlsSocketToServer().setEnabledProtocols(enabledProtocols);
/*
* The communication session setup timeout needs to be set here in case there is any problem with the
* TLS handshake.
* The timeout value will be overwritten with every new message being sent
*/
getTlsSocketToServer().setSoTimeout(TimeRestrictions.V2G_EVCC_COMMUNICATION_SETUP_TIMEOUT);
getLogger().debug("Starting TLS handshake ...");
getTlsSocketToServer().startHandshake();
getLogger().debug("TLS handshake finished");
Certificate[] seccCertificates = getTlsSocketToServer().getSession().getPeerCertificates();
X509Certificate seccLeafCertificate = (X509Certificate) seccCertificates[0];
// Check domain component of SECC certificate
if (!SecurityUtils.verifyDomainComponent(seccLeafCertificate, "CPO")) {
getLogger().error("TLS client connection failed. \n\t" +
"Reason: Domain component of SECC certificate not valid, expected 'DC=CPO'. \n\t" +
"Distinuished name of SECC certificate: " + seccLeafCertificate.getSubjectX500Principal().getName());
return false;
}
getLogger().info("TLS client connection established \n\t from link-local address " +
getClientAddress() + " and port " + getClientPort() +
"\n\t to host " + host.getHostAddress() + " and port " + port);
return true;
} catch (UnknownHostException e) {
getLogger().error("TLS client connection failed (UnknownHostException)!", e);
} catch (SSLHandshakeException e) {
getLogger().error("TLS client connection failed (SSLHandshakeException)", e);
} catch (SocketTimeoutException e) {
getLogger().fatal("TLS client connection failed (SocketTimeoutException) due to session setup timeout", e);
} catch (IOException e) {
getLogger().error("TLS client connection failed (IOException)!", e);
} catch (NullPointerException e) {
getLogger().fatal("NullPointerException while trying to set keystores, resource path to keystore/truststore might be incorrect");
}
return false;
}
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
if (getTimeout() >= 0) {
try {
getTlsSocketToServer().setSoTimeout(getTimeout());
if (!processIncomingMessage()) break;
} catch (SocketTimeoutException e) {
stopAndNotify("A timeout occurred while waiting for response message", null);
break;
} catch (IOException e2) {
stopAndNotify("An IOException occurred while trying to read message", e2);
break;
}
} else {
stopAndNotify("Timeout value is negative: " + getTimeout(), null);
break;
}
}
stop();
}
@Override
public void send(V2GTPMessage message, int timeout) {
setV2gTPMessage(null);
try {
getOutStream().write(message.getMessage());
getOutStream().flush();
getLogger().debug("Message sent");
setTimeout(timeout);
} catch (SSLHandshakeException e1) {
stopAndNotify("An SSLHandshakeException occurred", e1);
} catch (IOException e2) {
stopAndNotify("An undefined IOException occurred while trying to send message", e2);
}
}
@Override
public void stop() {
if (!isStopAlreadyInitiated()) {
getLogger().debug("Stopping TLS client ...");
setStopAlreadyInitiated(true);
try {
getInStream().close();
getOutStream().close();
getTlsSocketToServer().close();
uniqueTLSClientInstance = null;
Thread.currentThread().interrupt();
} catch (IOException e) {
getLogger().error("Error occurred while trying to close TCP socket to server", e);
}
getLogger().debug("TLS client stopped");
}
}
public SSLSocket getTlsSocketToServer() {
return tlsSocketToServer;
}
public void setTlsSocketToServer(SSLSocket tlsSocketToServer) {
this.tlsSocketToServer = tlsSocketToServer;
}
}

View File

@@ -0,0 +1,215 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*******************************************************************************/
package com.v2gclarity.risev2g.evcc.transportLayer;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.Inet6Address;
import java.net.MulticastSocket;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.v2gclarity.risev2g.shared.enumerations.GlobalValues;
import com.v2gclarity.risev2g.shared.misc.TimeRestrictions;
import com.v2gclarity.risev2g.shared.misc.V2GTPMessage;
import com.v2gclarity.risev2g.shared.utils.MiscUtils;
/**
* The UDP client is handling the SECCDiscovery messages only. The standard does not
* foresee any further communication to be done via UDP but TCP.
* Therefore, the size of the UPD packet to be received is restricted to 28 bytes
* (8 bytes header of V2GTP message + 20 byte SECCDiscoveryRes payload).
*/
public class UDPClient {
/*
* Eager instantiation of the singleton, since a UDP client is always needed upfront.
* The JVM creates the unique instance when the class is loaded and before any thread tries to
* access the instance variable -> thread safe.
*/
private Logger logger = LogManager.getLogger(this.getClass().getSimpleName());
private static UDPClient uniqueUDPClientInstance = new UDPClient();
private int multicastSocketPort;
private Inet6Address multicastAddress;
private MulticastSocket socketToUDPServer;
private byte[] udpServerResponse;
private DatagramPacket udpServerPacket;
private Inet6Address udpClientAddress;
private UDPClient() {
setUdpServerResponse(new byte[28]);
}
/**
* Used to check the correct initialization of a UDP client which is a prerequisite for establishing
* a V2G communication session.
* @return True if the initialization of the UDP client was successful, false otherwise
*/
public boolean initialize() {
setSocketToUDPServer(null);
// Try to find a free port within the range of V2G_UDP_SDP_Client (see Table 15)
while (getSocketToUDPServer() == null) {
try {
setUdpClientAddress(MiscUtils.getLinkLocalAddress());
if (getUdpClientAddress() == null) return false;
setMulticastAddress((Inet6Address) Inet6Address.getByName(GlobalValues.SDP_MULTICAST_ADDRESS.toString()));
setMulticastSocketPort(MiscUtils.getRandomPortNumber());
setSocketToUDPServer(new MulticastSocket(getMulticastSocketPort()));
// Without setting the interface, the UDP server will not receive the requests
getSocketToUDPServer().setInterface(getUdpClientAddress());
getSocketToUDPServer().joinGroup(getMulticastAddress());
getLogger().info("UDP client initialized at address " +
getUdpClientAddress().getHostAddress() + " and port " + getMulticastSocketPort());
return true;
} catch (UnknownHostException e) {
getLogger().error("Initialization of UDPClient failed (Unknown host exception)!", e);
return false;
} catch (IOException e) {
getLogger().debug("MulticastSocket creation failed, UDPClient port " + multicastSocketPort +
" may not be free, trying another port.", e);
setSocketToUDPServer(null);
return false;
}
}
return false;
}
public static synchronized UDPClient getInstance() {
if (uniqueUDPClientInstance == null) {
uniqueUDPClientInstance = new UDPClient();
}
return uniqueUDPClientInstance;
}
public byte[] send(V2GTPMessage v2gTPMessage) {
// Set up the UDP packet containing the V2GTP message to be sent to the UDP server
DatagramPacket udpClientPacket = new DatagramPacket(v2gTPMessage.getMessage(),
v2gTPMessage.getMessage().length,
getMulticastAddress(),
GlobalValues.V2G_UDP_SDP_SERVER_PORT.getShortValue());
setUdpServerPacket(new DatagramPacket(getUdpServerResponse(), getUdpServerResponse().length));
try {
getSocketToUDPServer().setSoTimeout(TimeRestrictions.SDP_RESPONSE_TIMEOUT);
getSocketToUDPServer().send(udpClientPacket);
getLogger().debug("Message sent");
// Wait for the UDP Response (receive() blocks until the data arrives)
getSocketToUDPServer().receive(getUdpServerPacket());
getLogger().debug("Message received");
return getUdpServerPacket().getData();
} catch (SocketTimeoutException e) {
getLogger().error("A SocketTimeoutException was thrown while waiting for input stream from UDPServer");
} catch (UnknownHostException e) {
getLogger().error("UDP request failed (UnknownHostException)!", e);
} catch (IOException e) {
getLogger().error("UDP request failed (IOException)!", e);
}
return null;
}
public void stop() {
getSocketToUDPServer().close();
getLogger().debug("UDP client stopped");
uniqueUDPClientInstance = null;
}
public Logger getLogger() {
return logger;
}
public int getMulticastSocketPort() {
return multicastSocketPort;
}
private void setMulticastSocketPort(int multicastSocketPort) {
this.multicastSocketPort = multicastSocketPort;
}
public Inet6Address getMulticastAddress() {
return multicastAddress;
}
private void setMulticastAddress(Inet6Address multicastAddress) {
this.multicastAddress = multicastAddress;
}
public MulticastSocket getSocketToUDPServer() {
return socketToUDPServer;
}
private void setSocketToUDPServer(MulticastSocket socketToUDPServer) {
this.socketToUDPServer = socketToUDPServer;
}
public byte[] getUdpServerResponse() {
return udpServerResponse;
}
private void setUdpServerResponse(byte[] udpServerResponse) {
this.udpServerResponse = udpServerResponse;
}
public DatagramPacket getUdpServerPacket() {
return udpServerPacket;
}
private void setUdpServerPacket(DatagramPacket udpServerPacket) {
this.udpServerPacket = udpServerPacket;
}
public Inet6Address getUdpClientAddress() {
return udpClientAddress;
}
private void setUdpClientAddress(Inet6Address udpClientAddress) {
this.udpClientAddress = udpClientAddress;
}
}

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Configuration>
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{ISO8601} %level [%t] %c: %m%n" />
</Console>
</Appenders>
<Loggers>
<Root level="debug">
<AppenderRef ref="Console" />
</Root>
</Loggers>
</Configuration>
<!-- see http://logging.apache.org/log4j/2.x/manual/configuration.html -->