* ClientAssociation
is obtained using ClientSap
. An association object can be
* used to execute the IEC 61850 ACSI services. Note that not all ACSI services have a corresponding
* function in this API. For example all GetDirectory and GetDefinition services are covered by
* retrieveModel()
. The control services can be executed by using getDataValues and
* setDataValues on the control objects in the data model.
*/
public final class ClientAssociation {
private static final Integer16 version = new Integer16(new byte[] {(byte) 0x01, (byte) 0x01});
private static final ParameterSupportOptions proposedParameterCbbBitString =
new ParameterSupportOptions(new byte[] {0x03, 0x05, (byte) 0xf1, 0x00});
private final ClientReceiver clientReceiver;
private final BlockingQueueServiceError
will be thrown.
*
* @param modelNode the functionally constrained model node that is to be read.
* @throws ServiceError if a ServiceError is returned by the server.
* @throws IOException if a fatal association error occurs. The association object will be closed
* and can no longer be used after this exception is thrown.
*/
public void getDataValues(FcModelNode modelNode) throws ServiceError, IOException {
ConfirmedServiceRequest serviceRequest = constructGetDataValuesRequest(modelNode);
ConfirmedServiceResponse confirmedServiceResponse = encodeWriteReadDecode(serviceRequest);
decodeGetDataValuesResponse(confirmedServiceResponse, modelNode);
}
private boolean decodeGetFileDirectoryResponse(
ConfirmedServiceResponse confirmedServiceResponse, ListServiceError
will be thrown. In this case it is not possible to find out which of
* several Basic Data Attributes could not be written.
*
* @param modelNode the functionally constrained model node that is to be written.
* @throws ServiceError if a ServiceError is returned by the server.
* @throws IOException if a fatal association error occurs. The association object will be closed
* and can no longer be used after this exception is thrown.
*/
public void setDataValues(FcModelNode modelNode) throws ServiceError, IOException {
ConfirmedServiceRequest serviceRequest = constructSetDataValuesRequest(modelNode);
ConfirmedServiceResponse confirmedServiceResponse = encodeWriteReadDecode(serviceRequest);
decodeSetDataValuesResponse(confirmedServiceResponse);
}
private ConfirmedServiceRequest constructSetDataValuesRequest(FcModelNode modelNode)
throws ServiceError {
VariableAccessSpecification variableAccessSpecification =
constructVariableAccessSpecification(modelNode);
ListOfData listOfData = new ListOfData();
List dataList = listOfData.getData();
dataList.add(modelNode.getMmsDataObj());
WriteRequest writeRequest = new WriteRequest();
writeRequest.setListOfData(listOfData);
writeRequest.setVariableAccessSpecification(variableAccessSpecification);
ConfirmedServiceRequest confirmedServiceRequest = new ConfirmedServiceRequest();
confirmedServiceRequest.setWrite(writeRequest);
return confirmedServiceRequest;
}
private VariableAccessSpecification constructVariableAccessSpecification(FcModelNode modelNode) {
VariableDefs listOfVariable = new VariableDefs();
List
* null
if reading was successful and a ServiceError if reading of this member failed.
*
* @param dataSet the DataSet that is to be read.
* @return a list indicating ServiceErrors that may have occurred.
* @throws IOException if a fatal IO error occurs. The association object will be closed and can
* no longer be used after this exception is thrown.
*/
public ListThe data set reference as it is set in an RCB must contain a dollar sign instead of a dot to * separate the logical node from the data set name, e.g.: 'LDevice1/LNode$DataSetName'. Therefore * his method will check the reference for a dot and if necessary convert it to a '$' sign before * sending the request to the server. * *
The parameters PurgeBuf, EntryId are only applicable if the given rcb is of type BRCB.
*
* @param rcb the report control block
* @param setRptId whether to set the report ID
* @param setDatSet whether to set the data set
* @param setOptFlds whether to set the optional fields
* @param setBufTm whether to set the buffer time
* @param setTrgOps whether to set the trigger options
* @param setIntgPd whether to set the integrity period
* @param setPurgeBuf whether to set purge buffer
* @param setEntryId whether to set the entry ID
* @return a list indicating ServiceErrors that may have occurred.
* @throws IOException if a fatal IO error occurs. The association object will be closed and can
* no longer be used after this exception is thrown.
*/
public List The selection is canceled in one of the following events:
*
*
*
*
* @param controlDataObject needs to be a controllable Data Object that contains a Data Attribute
* named "SBO".
* @return false if the selection/reservation was not successful (because it is already selected
* by another client). Otherwise true is returned.
* @throws ServiceError if a ServiceError is returned by the server.
* @throws IOException if a fatal IO error occurs. The association object will be closed and can
* no longer be used after this exception is thrown.
*/
public boolean select(FcModelNode controlDataObject) throws ServiceError, IOException {
BdaVisibleString sbo;
try {
sbo = (BdaVisibleString) controlDataObject.getChild("SBO");
} catch (Exception e) {
throw new IllegalArgumentException(
"ModelNode needs to conain a child node named SBO in order to select");
}
getDataValues(sbo);
return sbo.getValue().length != 0;
}
/**
* Executes the Operate ACSI Service on the given controllable Data Object (DO). The following
* subnodes of the given control DO should be set according your needs before calling this
* function. (Note that you can probably leave most attributes with their default value):
*
*
*
*
* All other operate parameters are automatically handled by this function.
*
* @param controlDataObject needs to be a controllable Data Object that contains a Data Attribute
* named "Oper".
* @throws ServiceError if a ServiceError is returned by the server
* @throws IOException if a fatal IO error occurs. The association object will be closed and can
* no longer be used after this exception is thrown.
*/
public void operate(FcModelNode controlDataObject) throws ServiceError, IOException {
ConstructedDataAttribute oper;
try {
oper = (ConstructedDataAttribute) controlDataObject.getChild("Oper");
} catch (Exception e) {
throw new IllegalArgumentException("ModelNode needs to conain a child node named \"Oper\".");
}
((BdaInt8U) oper.getChild("ctlNum")).setValue((short) 1);
((BdaTimestamp) oper.getChild("T")).setDate(new Date(System.currentTimeMillis()));
setDataValues(oper);
}
public boolean isOpen() {
return !closed;
}
/** Will close the connection simply by closing the TCP socket. */
public void close() {
clientReceiver.close(new IOException("Connection closed by client"));
}
/** Will send a disconnect request first and then close the TCP socket. */
public void disconnect() {
clientReceiver.disconnect();
}
final class ClientReceiver extends Thread {
private final ByteBuffer pduBuffer;
private Integer expectedResponseId;
private IOException lastIOException = null;
public ClientReceiver(int maxMmsPduSize) {
pduBuffer = ByteBuffer.allocate(maxMmsPduSize + 400);
}
@Override
public void run() {
try {
while (true) {
pduBuffer.clear();
byte[] buffer;
try {
buffer = acseAssociation.receive(pduBuffer);
} catch (TimeoutException e) {
// Illegal state: A timeout exception was thrown.
throw new IllegalStateException();
} catch (DecodingException e) {
// Error decoding the OSI headers of the received packet
continue;
}
MMSpdu decodedResponsePdu = new MMSpdu();
try {
decodedResponsePdu.decode(new ByteArrayInputStream(buffer), null);
} catch (IOException e) {
// Error decoding the received MMS PDU
continue;
}
if (decodedResponsePdu.getUnconfirmedPDU() != null) {
if (decodedResponsePdu
.getUnconfirmedPDU()
.getService()
.getInformationReport()
.getVariableAccessSpecification()
.getListOfVariable()
!= null) {
// Discarding LastApplError Report
} else {
if (reportListener != null) {
final Report report = processReport(decodedResponsePdu);
Thread t1 =
new Thread(
new Runnable() {
@Override
public void run() {
reportListener.newReport(report);
}
});
t1.start();
} else {
// discarding report because no ReportListener was registered.
}
}
} else if (decodedResponsePdu.getRejectPDU() != null) {
synchronized (incomingResponses) {
if (expectedResponseId == null) {
// Discarding Reject MMS PDU because no listener for request was found.
continue;
} else if (decodedResponsePdu.getRejectPDU().getOriginalInvokeID().value.intValue()
!= expectedResponseId) {
// Discarding Reject MMS PDU because no listener with fitting invokeID was found.
continue;
} else {
try {
incomingResponses.put(decodedResponsePdu);
} catch (InterruptedException e) {
}
}
}
} else if (decodedResponsePdu.getConfirmedErrorPDU() != null) {
synchronized (incomingResponses) {
if (expectedResponseId == null) {
// Discarding ConfirmedError MMS PDU because no listener for request was found.
continue;
} else if (decodedResponsePdu.getConfirmedErrorPDU().getInvokeID().value.intValue()
!= expectedResponseId) {
// Discarding ConfirmedError MMS PDU because no listener with fitting invokeID was
// found.
continue;
} else {
try {
incomingResponses.put(decodedResponsePdu);
} catch (InterruptedException e) {
}
}
}
} else {
synchronized (incomingResponses) {
if (expectedResponseId == null) {
// Discarding ConfirmedResponse MMS PDU because no listener for request was found.
continue;
} else if (decodedResponsePdu.getConfirmedResponsePDU().getInvokeID().value.intValue()
!= expectedResponseId) {
// Discarding ConfirmedResponse MMS PDU because no listener with fitting invokeID
// was
// found.
continue;
} else {
try {
incomingResponses.put(decodedResponsePdu);
} catch (InterruptedException e) {
}
}
}
}
}
} catch (IOException e) {
close(e);
} catch (Exception e) {
close(new IOException("unexpected exception while receiving", e));
}
}
public void setResponseExpected(int invokeId) {
expectedResponseId = invokeId;
}
private void disconnect() {
synchronized (this) {
if (closed == false) {
closed = true;
acseAssociation.disconnect();
lastIOException = new IOException("Connection disconnected by client");
if (reportListener != null) {
Thread t1 =
new Thread(
new Runnable() {
@Override
public void run() {
reportListener.associationClosed(lastIOException);
}
});
t1.start();
}
MMSpdu mmsPdu = new MMSpdu();
mmsPdu.setConfirmedRequestPDU(new ConfirmedRequestPDU());
try {
incomingResponses.put(mmsPdu);
} catch (InterruptedException e1) {
}
}
}
}
private void close(IOException e) {
synchronized (this) {
if (closed == false) {
closed = true;
acseAssociation.close();
lastIOException = e;
Thread t1 =
new Thread(
new Runnable() {
@Override
public void run() {
reportListener.associationClosed(lastIOException);
}
});
t1.start();
MMSpdu mmsPdu = new MMSpdu();
mmsPdu.setConfirmedRequestPDU(new ConfirmedRequestPDU());
try {
incomingResponses.put(mmsPdu);
} catch (InterruptedException e1) {
}
}
}
}
IOException getLastIOException() {
return lastIOException;
}
MMSpdu removeExpectedResponse() {
synchronized (incomingResponses) {
expectedResponseId = null;
return incomingResponses.poll();
}
}
}
}