diff --git a/src/main/java/com/beanit/iec61850bean/ClientAssociation.java b/src/main/java/com/beanit/iec61850bean/ClientAssociation.java index 866ae46..7cb6085 100644 --- a/src/main/java/com/beanit/iec61850bean/ClientAssociation.java +++ b/src/main/java/com/beanit/iec61850bean/ClientAssociation.java @@ -78,11 +78,7 @@ import java.net.InetAddress; import java.nio.ByteBuffer; import java.text.ParseException; import java.time.Instant; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.Collection; -import java.util.Iterator; -import java.util.List; +import java.util.*; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; @@ -117,6 +113,9 @@ public final class ClientAssociation { private boolean closed = false; + // maximum read quantity at once batch read + private int maxNodeSize = 1000; + ClientAssociation( InetAddress address, int port, @@ -809,6 +808,84 @@ public final class ClientAssociation { decodeGetDataValuesResponse(confirmedServiceResponse, modelNode); } + /** + * read batch data values. + * + * @param nodeList the functionally constrained model node that is to be read. + * @return the map of nodes that could not be read or failed. key - ModelNode; value - ErrorMessage + * @throws ServiceError + * @throws IOException + */ + public Map getDataValueList(List nodeList) throws ServiceError, IOException { + Map errorNodes = new HashMap<>(); + if (nodeList.size() > maxNodeSize) { + for (int i = 0; i < nodeList.size(); i += maxNodeSize) { + errorNodes.putAll(doBatchRead(nodeList.subList(i, Math.min(i + maxNodeSize, nodeList.size())))); + } + } else { + errorNodes.putAll(doBatchRead(nodeList)); + } + return errorNodes; + } + + private Map doBatchRead(List nodeList) throws ServiceError, IOException { + ConfirmedServiceRequest serviceRequest = constructGetDataValueListRequest(nodeList); + ConfirmedServiceResponse confirmedServiceResponse = encodeWriteReadDecode(serviceRequest); + return decodeGetDataValueListResponse(confirmedServiceResponse, nodeList); + } + + private ConfirmedServiceRequest constructGetDataValueListRequest(List nodeList) { + VariableAccessSpecification varAccessSpec = constructVariableAccessSpecification(nodeList); + + ReadRequest readRequest = new ReadRequest(); + readRequest.setVariableAccessSpecification(varAccessSpec); + + ConfirmedServiceRequest confirmedServiceRequest = new ConfirmedServiceRequest(); + confirmedServiceRequest.setRead(readRequest); + + return confirmedServiceRequest; + } + + /** + * @param confirmedServiceResponse response from the server + * @param nodeList list of nodes to read + * @return the set of nodes that have failed + * @throws ServiceError + */ + private Map decodeGetDataValueListResponse( + ConfirmedServiceResponse confirmedServiceResponse, List nodeList) throws ServiceError { + + if (confirmedServiceResponse.getRead() == null) { + throw new ServiceError( + ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT, + "Error decoding GetDataValuesReponsePdu"); + } + + List listOfAccessResults = + confirmedServiceResponse.getRead().getListOfAccessResult().getAccessResult(); + + if (listOfAccessResults.size() != nodeList.size()) { + throw new ServiceError( + ServiceError.PARAMETER_VALUE_INAPPROPRIATE, "Number of results does not match."); + } + Map errorNode = new HashMap<>(); + + for (int i = 0; i < listOfAccessResults.size(); i++) { + AccessResult accRes = listOfAccessResults.get(i); + + if (accRes.getFailure() != null) { + errorNode.put(nodeList.get(i), mmsDataAccessErrorToServiceError(accRes.getFailure()).getMessage()); + } else { + try { + nodeList.get(i).setValueFromMmsDataObj(accRes.getSuccess()); + } catch (ServiceError e) { + errorNode.put(nodeList.get(i), e.getMessage()); + } + } + } + return errorNode; + } + private boolean decodeGetFileDirectoryResponse( ConfirmedServiceResponse confirmedServiceResponse, List files) throws ServiceError { @@ -1158,6 +1235,20 @@ public final class ClientAssociation { return variableAccessSpecification; } + private VariableAccessSpecification constructVariableAccessSpecification(List nodeList) { + VariableDefs listOfVariable = new VariableDefs(); + + List variableDefsSeqOf = listOfVariable.getSEQUENCE(); + for (FcModelNode modelNodes : nodeList) { + variableDefsSeqOf.add(modelNodes.getMmsVariableDef()); + } + + VariableAccessSpecification variableAccessSpecification = new VariableAccessSpecification(); + variableAccessSpecification.setListOfVariable(listOfVariable); + + return variableAccessSpecification; + } + private void decodeSetDataValuesResponse(ConfirmedServiceResponse confirmedServiceResponse) throws ServiceError {