add files
This commit is contained in:
1
temp/RISE-V2G/RISE-V2G-EVCC/.gitignore
vendored
Normal file
1
temp/RISE-V2G/RISE-V2G-EVCC/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/target
|
||||
144
temp/RISE-V2G/RISE-V2G-EVCC/EVCCConfig.properties
Normal file
144
temp/RISE-V2G/RISE-V2G-EVCC/EVCCConfig.properties
Normal 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
|
||||
23
temp/RISE-V2G/RISE-V2G-EVCC/LICENSE.txt
Normal file
23
temp/RISE-V2G/RISE-V2G-EVCC/LICENSE.txt
Normal 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.
|
||||
*******************************************************************************/
|
||||
95
temp/RISE-V2G/RISE-V2G-EVCC/pom.xml
Normal file
95
temp/RISE-V2G/RISE-V2G-EVCC/pom.xml
Normal 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>
|
||||
18
temp/RISE-V2G/RISE-V2G-EVCC/src/assembly/bin.xml
Normal file
18
temp/RISE-V2G/RISE-V2G-EVCC/src/assembly/bin.xml
Normal 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>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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() + ")");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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() + ")");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
14
temp/RISE-V2G/RISE-V2G-EVCC/src/main/resources/log4j2.xml
Normal file
14
temp/RISE-V2G/RISE-V2G-EVCC/src/main/resources/log4j2.xml
Normal 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 -->
|
||||
Reference in New Issue
Block a user