Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add OCPP 2.0.1 and multi-protocol support #239

Merged
merged 7 commits into from
Sep 30, 2023
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
4 changes: 2 additions & 2 deletions OCPP-J/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>eu.chargetime.ocpp</groupId>
<artifactId>OCPP-J</artifactId>
<version>1.0.2</version>
<version>1.2.0</version>
<packaging>jar</packaging>

<name>Java-OCA-OCPP OCPP-J</name>
Expand Down Expand Up @@ -50,7 +50,7 @@
<dependency>
<groupId>eu.chargetime.ocpp</groupId>
<artifactId>common</artifactId>
<version>1.0.2</version>
<version>1.2.0</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
Expand Down
81 changes: 50 additions & 31 deletions OCPP-J/src/main/java/eu/chargetime/ocpp/JSONCommunicator.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@
import eu.chargetime.ocpp.model.CallErrorMessage;
import eu.chargetime.ocpp.model.CallMessage;
import eu.chargetime.ocpp.model.CallResultMessage;
import eu.chargetime.ocpp.model.Message;
import eu.chargetime.ocpp.model.Exclude;

import eu.chargetime.ocpp.model.Message;
import java.lang.reflect.Type;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
Expand Down Expand Up @@ -79,6 +78,16 @@ public JSONCommunicator(Radio radio) {
super(radio);
}

/**
* Handle required injections.
*
* @param radio instance of the {@link Radio}.
* @param enableTransactionQueue true if transaction queue should be enabled.
*/
public JSONCommunicator(Radio radio, boolean enableTransactionQueue) {
super(radio, enableTransactionQueue);
}

private static class ZonedDateTimeSerializer
implements JsonSerializer<ZonedDateTime>, JsonDeserializer<ZonedDateTime> {

Expand All @@ -101,17 +110,18 @@ public ZonedDateTime deserialize(
static {
GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(ZonedDateTime.class, new ZonedDateTimeSerializer());
builder.addSerializationExclusionStrategy(new ExclusionStrategy() {
@Override
public boolean shouldSkipClass(Class<?> clazz) {
return false;
}

@Override
public boolean shouldSkipField(FieldAttributes field) {
return field.getAnnotation(Exclude.class) != null;
}
});
builder.addSerializationExclusionStrategy(
new ExclusionStrategy() {
@Override
public boolean shouldSkipClass(Class<?> clazz) {
return false;
}

@Override
public boolean shouldSkipField(FieldAttributes field) {
return field.getAnnotation(Exclude.class) != null;
}
});

gson = builder.disableHtmlEscaping().create();
}
Expand Down Expand Up @@ -147,26 +157,35 @@ protected Message parse(Object json) {
Message message;
JsonParser parser = new JsonParser();
JsonArray array = parser.parse(json.toString()).getAsJsonArray();

if (array.get(INDEX_MESSAGEID).getAsInt() == TYPENUMBER_CALL) {
message = new CallMessage();
message.setAction(array.get(INDEX_CALL_ACTION).getAsString());
message.setPayload(array.get(INDEX_CALL_PAYLOAD).toString());
} else if (array.get(INDEX_MESSAGEID).getAsInt() == TYPENUMBER_CALLRESULT) {
message = new CallResultMessage();
message.setPayload(array.get(INDEX_CALLRESULT_PAYLOAD).toString());
} else if (array.get(INDEX_MESSAGEID).getAsInt() == TYPENUMBER_CALLERROR) {
message = new CallErrorMessage();
((CallErrorMessage) message).setErrorCode(array.get(INDEX_CALLERROR_ERRORCODE).getAsString());
((CallErrorMessage) message)
.setErrorDescription(array.get(INDEX_CALLERROR_DESCRIPTION).getAsString());
((CallErrorMessage) message).setRawPayload(array.get(INDEX_CALLERROR_PAYLOAD).toString());
} else {
logger.error("Unknown message type of message: {}", json.toString());
throw new IllegalArgumentException("Unknown message type");
String messageId = "-1";

try {
messageId = array.get(INDEX_UNIQUEID).getAsString();
if (array.get(INDEX_MESSAGEID).getAsInt() == TYPENUMBER_CALL) {
message = new CallMessage();
message.setAction(array.get(INDEX_CALL_ACTION).getAsString());
message.setPayload(array.get(INDEX_CALL_PAYLOAD).toString());
} else if (array.get(INDEX_MESSAGEID).getAsInt() == TYPENUMBER_CALLRESULT) {
message = new CallResultMessage();
message.setPayload(array.get(INDEX_CALLRESULT_PAYLOAD).toString());
} else if (array.get(INDEX_MESSAGEID).getAsInt() == TYPENUMBER_CALLERROR) {
message = new CallErrorMessage();
((CallErrorMessage) message).setErrorCode(array.get(INDEX_CALLERROR_ERRORCODE).getAsString());
((CallErrorMessage) message)
.setErrorDescription(array.get(INDEX_CALLERROR_DESCRIPTION).getAsString());
((CallErrorMessage) message).setRawPayload(array.get(INDEX_CALLERROR_PAYLOAD).toString());
} else {
logger.error("Unknown message type of message: {}", json.toString());
sendCallError(messageId, null, "MessageTypeNotSupported", null);
return null;
}
} catch (Exception e) {
logger.error("Exception while parsing message: {}", json.toString());
sendCallError(messageId, null, "RpcFrameworkError", e.getMessage());
return null;
}

message.setId(array.get(INDEX_UNIQUEID).getAsString());
message.setId(messageId);

return message;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public class JSONConfiguration {
public static final String PING_INTERVAL_PARAMETER = "PING_INTERVAL";
public static final String USERNAME_PARAMETER = "USERNAME";
public static final String PASSWORD_PARAMETER = "PASSWORD";
public static final String CONNECT_NON_BLOCKING_PARAMETER = "CONNECT_NON_BLOCKING";
public static final String CONNECT_TIMEOUT_IN_MS_PARAMETER = "CONNECT_TIMEOUT_IN_MS";
public static final String WEBSOCKET_WORKER_COUNT = "WEBSOCKET_WORKER_COUNT";

Expand Down
58 changes: 45 additions & 13 deletions OCPP-J/src/main/java/eu/chargetime/ocpp/WebSocketTransmitter.java
Original file line number Diff line number Diff line change
Expand Up @@ -134,13 +134,24 @@ public void onError(Exception ex) {

configure();

logger.debug("Trying to connect to: {}", resource);
boolean isNonBlocking = isNonBlockingParameterSet();

try {
client.connectBlocking();
closed = false;
} catch (Exception ex) {
logger.warn("client.connectBlocking() failed", ex);
logger.debug("Trying to connect to: {}{}", resource, isNonBlocking ? "" : " [blocking]");

if (isNonBlocking) {
try {
client.connect();
closed = false;
} catch (Exception ex) {
logger.warn("client.connect() failed", ex);
}
} else {
try {
client.connectBlocking();
closed = false;
} catch (Exception ex) {
logger.warn("client.connectBlocking() failed", ex);
}
}
}

Expand Down Expand Up @@ -175,16 +186,37 @@ public void disconnect() {
if (client == null) {
return;
}
try {
client.closeBlocking();
} catch (Exception ex) {
logger.info("client.closeBlocking() failed", ex);
} finally {
client = null;
closed = true;

boolean isNonBlocking = isNonBlockingParameterSet();

logger.debug("Disconnecting{}", isNonBlocking ? "" : " [blocking]");

if (isNonBlocking) {
try {
client.close();
} catch (Exception ex) {
logger.info("client.close() failed", ex);
} finally {
client = null;
closed = true;
}
} else {
try {
client.closeBlocking();
} catch (Exception ex) {
logger.info("client.closeBlocking() failed", ex);
} finally {
client = null;
closed = true;
}
}
}

private boolean isNonBlockingParameterSet() {
Object rawParam = configuration.getParameter(JSONConfiguration.CONNECT_NON_BLOCKING_PARAMETER);
return rawParam instanceof Boolean ? (Boolean) rawParam : false;
}

@Override
public void send(Object request) throws NotConnectedException {
if (client == null) {
Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ buildscript {

allprojects {
group = 'eu.chargetime.ocpp'
version = '1.1'
version = '1.2'
}

subprojects {
Expand Down
2 changes: 1 addition & 1 deletion ocpp-common/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>eu.chargetime.ocpp</groupId>
<artifactId>common</artifactId>
<version>1.0.2</version>
<version>1.2.0</version>

<name>Java-OCA-OCPP common</name>
<description>Implementation of Open Charge-Point Protocol common library.</description>
Expand Down
10 changes: 4 additions & 6 deletions ocpp-common/src/main/java/eu/chargetime/ocpp/Client.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,22 +44,20 @@ of this software and associated documentation files (the "Software"), to deal
public class Client {
private static final Logger logger = LoggerFactory.getLogger(Client.class);

private ISession session;
private final IFeatureRepository featureRepository;
private final ISession session;
private final IPromiseRepository promiseRepository;

/**
* Handle required injections.
*
* @param session Inject session object
* @param promiseRepository Inject promise repository
* @see Session
*/
public Client(
ISession session,
IFeatureRepository featureRepository,
robert-s-ubi marked this conversation as resolved.
Show resolved Hide resolved
IPromiseRepository promiseRepository) {
this.session = session;
this.featureRepository = featureRepository;
this.promiseRepository = promiseRepository;
}

Expand Down Expand Up @@ -88,7 +86,7 @@ public void handleConfirmation(String uniqueId, Confirmation confirmation) {

@Override
public Confirmation handleRequest(Request request) throws UnsupportedFeatureException {
Optional<Feature> featureOptional = featureRepository.findFeature(request);
Optional<Feature> featureOptional = session.getFeatureRepository().findFeature(request);
if (featureOptional.isPresent()) {
return featureOptional.get().handleRequest(getSessionId(), request);
} else {
Expand Down Expand Up @@ -149,7 +147,7 @@ public void disconnect() {
*/
public CompletableFuture<Confirmation> send(Request request)
throws UnsupportedFeatureException, OccurenceConstraintException {
Optional<Feature> featureOptional = featureRepository.findFeature(request);
Optional<Feature> featureOptional = session.getFeatureRepository().findFeature(request);
if (!featureOptional.isPresent()) {
logger.error("Can't send request: unsupported feature. Payload: {}", request);
throw new UnsupportedFeatureException();
Expand Down
35 changes: 26 additions & 9 deletions ocpp-common/src/main/java/eu/chargetime/ocpp/Communicator.java
Original file line number Diff line number Diff line change
Expand Up @@ -115,9 +115,20 @@ protected abstract Object makeCallError(
* @param transmitter Injected {@link Transmitter}
*/
public Communicator(Radio transmitter) {
this(transmitter, true);
}

/**
* Handle required injections.
*
* @param transmitter Injected {@link Transmitter}
* @param enableTransactionQueue flag to enable/disable the transaction queue and associated
* processing
*/
public Communicator(Radio transmitter, boolean enableTransactionQueue) {
this.radio = transmitter;
this.transactionQueue = new ArrayDeque<>();
this.retryRunner = new RetryRunner();
this.transactionQueue = enableTransactionQueue ? new ArrayDeque<>() : null;
this.retryRunner = enableTransactionQueue ? new RetryRunner() : null;
this.failedFlag = false;
}

Expand Down Expand Up @@ -164,7 +175,7 @@ public synchronized void sendCall(String uniqueId, String action, Request reques

try {
if (radio.isClosed()) {
if (request.transactionRelated()) {
if (request.transactionRelated() && transactionQueue != null) {
logger.warn("Not connected: storing request to queue: {}", request);
transactionQueue.add(call);
} else {
Expand All @@ -175,15 +186,17 @@ public synchronized void sendCall(String uniqueId, String action, Request reques
"The request can't be sent due to the lack of connection",
request);
}
} else if (request.transactionRelated() && transactionQueue.size() > 0) {
} else if (request.transactionRelated()
&& transactionQueue != null
&& transactionQueue.size() > 0) {
transactionQueue.add(call);
processTransactionQueue();
} else {
radio.send(call);
}
} catch (NotConnectedException ex) {
logger.warn("sendCall() failed: not connected");
if (request.transactionRelated()) {
if (request.transactionRelated() && transactionQueue != null) {
transactionQueue.add(call);
} else {
events.onError(
Expand Down Expand Up @@ -211,7 +224,11 @@ public void sendCallResult(String uniqueId, String action, Confirmation confirma
try {
completedHandler.onConfirmationCompleted();
} catch (Throwable e) {
events.onError(uniqueId, "ConfirmationCompletedHandlerFailed", "The confirmation completed callback handler failed with exception " + e.toString(), confirmation);
events.onError(
uniqueId,
"ConfirmationCompletedHandlerFailed",
"The confirmation completed callback handler failed with exception " + e.toString(),
confirmation);
}
}
} catch (NotConnectedException ex) {
Expand Down Expand Up @@ -257,7 +274,7 @@ public void disconnect() {
}

private synchronized void processTransactionQueue() {
if (!retryRunner.isAlive()) {
if (retryRunner != null && !retryRunner.isAlive()) {
if (retryRunner.getState() != Thread.State.NEW) {
retryRunner = new RetryRunner();
}
Expand Down Expand Up @@ -315,7 +332,7 @@ public void disconnected() {
*/
private Object getRetryMessage() {
Object result = null;
if (!transactionQueue.isEmpty()) result = transactionQueue.peek();
if (transactionQueue != null && !transactionQueue.isEmpty()) result = transactionQueue.peek();
return result;
}

Expand All @@ -329,7 +346,7 @@ private boolean hasFailed() {
}

private void popRetryMessage() {
if (!transactionQueue.isEmpty()) transactionQueue.pop();
if (transactionQueue != null && !transactionQueue.isEmpty()) transactionQueue.pop();
}

/** Will resend transaction related requests. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,23 @@ of this software and associated documentation files (the "Software"), to deal

public class FeatureRepository implements IFeatureRepository {

private final ProtocolVersion protocolVersion;
private final Map<String, Feature> actionMap = new HashMap<>();
private final Map<Class<?>, Feature> classMap = new HashMap<>();

public FeatureRepository() {
this(ProtocolVersion.OCPP1_6);
}

public FeatureRepository(ProtocolVersion protocolVersion) {
this.protocolVersion = protocolVersion;
}

@Override
public ProtocolVersion getProtocolVersion() {
return protocolVersion;
}

/**
* Add {@link Profile} to support a group of features.
*
Expand Down Expand Up @@ -89,6 +103,7 @@ public Optional<Feature> findFeature(Object needle) {
@Override
public String toString() {
return MoreObjects.toStringHelper("FeatureRepository")
.add("protocolVersion", protocolVersion)
.add("actionMap", actionMap)
.add("classMap", classMap)
.toString();
Expand Down
Loading