- IEC 61850 client: added async versions for connect, abort, release, get variable specification, write object

pull/93/head
Michael Zillgith 7 years ago
parent a5e15003ce
commit 54d8fb74d7

@ -22,6 +22,7 @@ add_subdirectory(iec61850_client_example_reporting)
add_subdirectory(iec61850_client_example_log)
add_subdirectory(iec61850_client_example_array)
add_subdirectory(iec61850_client_example_files)
add_subdirectory(iec61850_client_example_async)
if(WIN32)
if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/../third_party/winpcap/Lib/wpcap.lib")

@ -0,0 +1,17 @@
set(iec61850_client_async_SRCS
client_example_async.c
)
IF(WIN32)
set_source_files_properties(${iec61850_client_async_SRCS}
PROPERTIES LANGUAGE CXX)
ENDIF(WIN32)
add_executable(iec61850_client_async
${iec61850_client_async_SRCS}
)
target_link_libraries(iec61850_client_async
iec61850
)

@ -0,0 +1,145 @@
/*
* client_example_async.c
*
* Shows how to use the asynchronous client API
*
* This example is intended to be used with server_example_basic_io or server_example_goose.
*/
#include "iec61850_client.h"
#include <stdlib.h>
#include <stdio.h>
#include "hal_thread.h"
void
reportCallbackFunction(void* parameter, ClientReport report)
{
MmsValue* dataSetValues = ClientReport_getDataSetValues(report);
printf("received report for %s\n", ClientReport_getRcbReference(report));
int i;
for (i = 0; i < 4; i++) {
ReasonForInclusion reason = ClientReport_getReasonForInclusion(report, i);
if (reason != IEC61850_REASON_NOT_INCLUDED) {
printf(" GGIO1.SPCSO%i.stVal: %i (included for reason %i)\n", i,
MmsValue_getBoolean(MmsValue_getElement(dataSetValues, i)), reason);
}
}
}
static void
printValue(char* name, MmsValue* value)
{
char buf[1000];
MmsValue_printToBuffer(value, buf, 1000);
printf("Received value for %s: %s\n", name, buf);
}
static void
readObjectHandler (int invokeId, void* parameter, IedClientError err, MmsValue* value)
{
if (err == IED_ERROR_OK) {
printValue((char*) parameter, value);
MmsValue_delete(value);
}
else {
printf("Failed to read object %s (err=%i)\n", parameter, err);
}
}
static void
getVarSpecHandler (int invokeId, void* parameter, IedClientError err, MmsVariableSpecification* spec)
{
if (err == IED_ERROR_OK) {
printf("variable: %s has type %d\n", (char*) parameter, MmsVariableSpecification_getType(spec));
MmsVariableSpecification_destroy(spec);
}
else {
printf("Failed to get variable specification for object %s (err=%i)\n", parameter, err);
}
}
int main(int argc, char** argv) {
char* hostname;
int tcpPort = 102;
if (argc > 1)
hostname = argv[1];
else
hostname = "localhost";
if (argc > 2)
tcpPort = atoi(argv[2]);
IedClientError error;
IedConnection con = IedConnection_create();
IedConnection_connectAsync(con, &error, hostname, tcpPort);
if (error == IED_ERROR_OK) {
bool success = true;
while (IedConnection_getState(con) != IED_STATE_CONNECTED) {
if (IedConnection_getState(con) == IED_STATE_CLOSED) {
success = false;
break;
}
Thread_sleep(10);
}
if (success) {
IedConnection_readObjectAsync(con, &error, "simpleIOGenericIO/GGIO1.AnIn1.mag.f", IEC61850_FC_MX, readObjectHandler, "simpleIOGenericIO/GGIO1.AnIn1.mag.f");
if (error != IED_ERROR_OK) {
printf("read object error %i\n", error);
}
IedConnection_readObjectAsync(con, &error, "simpleIOGenericIO/GGIO1.AnIn2.mag.f", IEC61850_FC_MX, readObjectHandler, "simpleIOGenericIO/GGIO1.AnIn2.mag.f");
if (error != IED_ERROR_OK) {
printf("read object error %i\n", error);
}
IedConnection_getVariableSpecificationAsync(con, &error, "simpleIOGenericIO/GGIO1.AnIn1", IEC61850_FC_MX, getVarSpecHandler, "simpleIOGenericIO/GGIO1.AnIn1");
if (error != IED_ERROR_OK) {
printf("get variable specification error %i\n", error);
}
}
Thread_sleep(1000);
IedConnection_releaseAsync(con, &error);
if (error != IED_ERROR_OK) {
printf("Release returned error: %d\n", error);
}
else {
while (IedConnection_getState(con) != IED_STATE_CLOSED) {
Thread_sleep(10);
}
}
}
else {
printf("Failed to connect to %s:%i\n", hostname, tcpPort);
}
IedConnection_destroy(con);
}

@ -494,6 +494,8 @@ createNewConnectionObject(TLSConfiguration tlsConfig)
self->outstandingCalls = (IedConnectionOutstandingCall) GLOBAL_CALLOC(OUTSTANDING_CALLS, sizeof(struct sIedConnectionOutstandingCall));
self->connectionTimeout = DEFAULT_CONNECTION_TIMEOUT;
MmsConnection_setInformationReportHandler(self->connection, informationReportHandler, self);
}
return self;
@ -545,6 +547,7 @@ IedConnection_installConnectionClosedHandler(IedConnection self, IedConnectionCl
self->connectionClosedParameter = parameter;
}
//TODO remove - not required - replace by mmsConnectionStateChangedHandler
static void
connectionLostHandler(MmsConnection connection, void* parameter)
{
@ -562,12 +565,12 @@ connectionLostHandler(MmsConnection connection, void* parameter)
void
IedConnection_connect(IedConnection self, IedClientError* error, const char* hostname, int tcpPort)
{
MmsError mmsError;
if (IedConnection_getState(self) != IED_STATE_CONNECTED) {
MmsError mmsError;
MmsConnection_setConnectionLostHandler(self->connection, NULL, NULL);
MmsConnection_setInformationReportHandler(self->connection, informationReportHandler, self);
MmsConnection_setConnectTimeout(self->connection, self->connectionTimeout);
@ -585,6 +588,44 @@ IedConnection_connect(IedConnection self, IedClientError* error, const char* hos
*error = IED_ERROR_ALREADY_CONNECTED;
}
static void
mmsConnectionStateChangedHandler(MmsConnection connection, void* parameter, MmsConnectionState newState)
{
IedConnection self = (IedConnection) parameter;
printf("state changed: %d\n", newState);
if (newState == MMS_CONNECTION_STATE_CONNECTED) {
IedConnection_setState(self, IED_STATE_CONNECTED);
MmsConnection_setConnectionLostHandler(self->connection, connectionLostHandler, (void*) self);
}
else if (newState == MMS_CONNECTION_STATE_CLOSED) {
IedConnection_setState(self, IED_STATE_CLOSED);
}
}
void
IedConnection_connectAsync(IedConnection self, IedClientError* error, const char* hostname, int tcpPort)
{
if (IedConnection_getState(self) != IED_STATE_CONNECTED) {
MmsError mmsError = MMS_ERROR_NONE;
MmsConnection_setConnectTimeout(self->connection, self->connectionTimeout);
MmsConnection_setConnectionLostHandler(self->connection, NULL, NULL);
//TODO move to createNewConnectionObject function
MmsConnection_setConnectionStateChangedHandler(self->connection, mmsConnectionStateChangedHandler, self);
MmsConnection_connectAsync(self->connection, &mmsError, hostname, tcpPort);
*error = iedConnection_mapMmsErrorToIedError(mmsError);
}
else
*error = IED_ERROR_ALREADY_CONNECTED;
}
void
IedConnection_abort(IedConnection self, IedClientError* error)
{
@ -601,6 +642,21 @@ IedConnection_abort(IedConnection self, IedClientError* error)
*error = IED_ERROR_NOT_CONNECTED;
}
void
IedConnection_abortAsync(IedConnection self, IedClientError* error)
{
if (IedConnection_getState(self) == IED_STATE_CONNECTED) {
MmsError mmsError;
MmsConnection_abortAsync(self->connection, &mmsError);
*error = iedConnection_mapMmsErrorToIedError(mmsError);
}
else
*error = IED_ERROR_NOT_CONNECTED;
}
void
IedConnection_release(IedConnection self, IedClientError* error)
{
@ -615,6 +671,20 @@ IedConnection_release(IedConnection self, IedClientError* error)
*error = IED_ERROR_NOT_CONNECTED;
}
void
IedConnection_releaseAsync(IedConnection self, IedClientError* error)
{
if (IedConnection_getState(self) == IED_STATE_CONNECTED) {
MmsError mmsError;
MmsConnection_concludeAsync(self->connection, &mmsError, NULL, NULL);
*error = iedConnection_mapMmsErrorToIedError(mmsError);
}
else
*error = IED_ERROR_NOT_CONNECTED;
}
void
IedConnection_close(IedConnection self)
{
@ -648,41 +718,6 @@ IedConnection_destroy(IedConnection self)
GLOBAL_FREEMEM(self);
}
MmsVariableSpecification*
IedConnection_getVariableSpecification(IedConnection self, IedClientError* error, const char* objectReference,
FunctionalConstraint fc)
{
char domainIdBuffer[65];
char itemIdBuffer[129];
char* domainId;
char* itemId;
MmsError mmsError;
MmsVariableSpecification* varSpec = NULL;
domainId = MmsMapping_getMmsDomainFromObjectReference(objectReference, domainIdBuffer);
itemId = MmsMapping_createMmsVariableNameFromObjectReference(objectReference, fc, itemIdBuffer);
if ((domainId == NULL) || (itemId == NULL)) {
*error = IED_ERROR_OBJECT_REFERENCE_INVALID;
goto cleanup_and_exit;
}
varSpec =
MmsConnection_getVariableAccessAttributes(self->connection, &mmsError, domainId, itemId);
if (varSpec != NULL)
*error = IED_ERROR_OK;
else
*error = iedConnection_mapMmsErrorToIedError(mmsError);
cleanup_and_exit:
return varSpec;
}
static IedConnectionOutstandingCall
allocateOutstandingCall(IedConnection self)
{
@ -739,11 +774,108 @@ lookupOutstandingCall(IedConnection self, uint32_t invokeId)
return call;
}
MmsVariableSpecification*
IedConnection_getVariableSpecification(IedConnection self, IedClientError* error, const char* objectReference,
FunctionalConstraint fc)
{
char domainIdBuffer[65];
char itemIdBuffer[129];
char* domainId;
char* itemId;
MmsError mmsError;
MmsVariableSpecification* varSpec = NULL;
domainId = MmsMapping_getMmsDomainFromObjectReference(objectReference, domainIdBuffer);
itemId = MmsMapping_createMmsVariableNameFromObjectReference(objectReference, fc, itemIdBuffer);
if ((domainId == NULL) || (itemId == NULL)) {
*error = IED_ERROR_OBJECT_REFERENCE_INVALID;
goto cleanup_and_exit;
}
varSpec =
MmsConnection_getVariableAccessAttributes(self->connection, &mmsError, domainId, itemId);
if (varSpec != NULL)
*error = IED_ERROR_OK;
else
*error = iedConnection_mapMmsErrorToIedError(mmsError);
cleanup_and_exit:
return varSpec;
}
static void
readObjectHandlerInternal(int invokeId, void* parameter, MmsError err, MmsValue* value)
getAccessAttrHandler(int invokeId, void* parameter, MmsError err, MmsVariableSpecification* typeSpec)
{
printf("readObjectHandlerInternal %i\n", invokeId);
IedConnection self = (IedConnection) parameter;
IedConnectionOutstandingCall call = lookupOutstandingCall(self, invokeId);
if (call) {
IedConnection_GetVariableSpecificationHandler handler = (IedConnection_GetVariableSpecificationHandler) call->callback;
handler(invokeId, call->callbackParameter, iedConnection_mapMmsErrorToIedError(err), typeSpec);
releaseOutstandingCall(self, call);
}
else {
if (DEBUG_IED_CLIENT)
printf("IED_CLIENT: internal error - no matching outstanding call!\n");
}
}
uint32_t
IedConnection_getVariableSpecificationAsync(IedConnection self, IedClientError* error, const char* dataAttributeReference,
FunctionalConstraint fc, IedConnection_GetVariableSpecificationHandler handler, void* parameter)
{
uint32_t invokeId = 0;
char domainIdBuffer[65];
char itemIdBuffer[129];
char* domainId;
char* itemId;
MmsError mmsError;
MmsVariableSpecification* varSpec = NULL;
domainId = MmsMapping_getMmsDomainFromObjectReference(dataAttributeReference, domainIdBuffer);
itemId = MmsMapping_createMmsVariableNameFromObjectReference(dataAttributeReference, fc, itemIdBuffer);
if ((domainId == NULL) || (itemId == NULL)) {
*error = IED_ERROR_OBJECT_REFERENCE_INVALID;
goto cleanup_and_exit;
}
IedConnectionOutstandingCall call = allocateOutstandingCall(self);
if (call == NULL) {
*error = IED_ERROR_OUTSTANDING_CALL_LIMIT_REACHED;
return 0;
}
call->callback = handler;
call->callbackParameter = parameter;
call->invokeId = MmsConnection_getVariableAccessAttributesAsync(self->connection, &mmsError, domainId, itemId, getAccessAttrHandler, self);
invokeId = call->invokeId;
*error = iedConnection_mapMmsErrorToIedError(mmsError);
cleanup_and_exit:
return invokeId;
}
static void
readObjectHandlerInternal(int invokeId, void* parameter, MmsError err, MmsValue* value)
{
IedConnection self = (IedConnection) parameter;
IedConnectionOutstandingCall call = lookupOutstandingCall(self, invokeId);
@ -757,7 +889,7 @@ readObjectHandlerInternal(int invokeId, void* parameter, MmsError err, MmsValue*
releaseOutstandingCall(self, call);
}
else {
// if (DEBUG_IED_CLIENT)
if (DEBUG_IED_CLIENT)
printf("IED_CLIENT: internal error - no matching outstanding call!\n");
}
}
@ -1144,6 +1276,102 @@ IedConnection_writeObject(IedConnection self, IedClientError* error, const char*
*error = iedConnection_mapMmsErrorToIedError(mmsError);
}
static void
writeVariableHandler(int invokeId, void* parameter, MmsError err, MmsDataAccessError accessError)
{
IedConnection self = (IedConnection) parameter;
IedConnectionOutstandingCall call = lookupOutstandingCall(self, invokeId);
if (call) {
IedConnection_WriteObjectHandler handler = (IedConnection_WriteObjectHandler) call->callback;
IedClientError iedError = iedConnection_mapMmsErrorToIedError(err);
if (iedError == IED_ERROR_OK)
iedError = iedConnection_mapDataAccessErrorToIedError(accessError);
handler(invokeId, call->callbackParameter, iedError);
releaseOutstandingCall(self, call);
}
else {
if (DEBUG_IED_CLIENT)
printf("IED_CLIENT: internal error - no matching outstanding call!\n");
}
}
typedef void
(*IedConnection_WriteObjectHandler) (int invokeId, void* parameter, IedClientError err);
uint32_t
IedConnection_writeObjectAsync(IedConnection self, IedClientError* error, const char* objectReference,
FunctionalConstraint fc, MmsValue* value, IedConnection_WriteObjectHandler handler, void* parameter)
{
char domainIdBuffer[65];
char itemIdBuffer[65];
char* domainId;
char* itemId;
domainId = MmsMapping_getMmsDomainFromObjectReference(objectReference, domainIdBuffer);
itemId = MmsMapping_createMmsVariableNameFromObjectReference(objectReference, fc, itemIdBuffer);
if ((domainId == NULL) || (itemId == NULL)) {
*error = IED_ERROR_OBJECT_REFERENCE_INVALID;
return 0;
}
IedConnectionOutstandingCall call = allocateOutstandingCall(self);
if (call == NULL) {
*error = IED_ERROR_OUTSTANDING_CALL_LIMIT_REACHED;
return 0;
}
call->callback = handler;
call->callbackParameter = parameter;
call->invokeId = 0;
MmsError err = MMS_ERROR_NONE;
/* check if item ID contains an array "(..)" */
char* brace = strchr(itemId, '(');
if (brace) {
char* secondBrace = strchr(brace, ')');
if (secondBrace) {
char* endPtr;
int index = (int) strtol(brace + 1, &endPtr, 10);
if (endPtr == secondBrace) {
char* component = NULL;
if (strlen(secondBrace + 1) > 1)
component = secondBrace + 2; /* skip "." after array element specifier */
*brace = 0;
call->invokeId = MmsConnection_writeSingleArrayElementWithComponentAsync(self->connection, &err, domainId, itemId, index, component, value,
writeVariableHandler, self);
}
else
*error = IED_ERROR_USER_PROVIDED_INVALID_ARGUMENT;
}
else
*error = IED_ERROR_USER_PROVIDED_INVALID_ARGUMENT;
}
else
call->invokeId = MmsConnection_writeVariableAsync(self->connection, &err, domainId, itemId, value, writeVariableHandler, self);
*error = iedConnection_mapMmsErrorToIedError(err);
return call->invokeId;
}
void
IedConnection_writeBooleanValue(IedConnection self, IedClientError* error, const char* objectReference,
FunctionalConstraint fc, bool value)

@ -235,6 +235,8 @@ IedConnection_setConnectTimeout(IedConnection self, uint32_t timeoutInMs);
/**
* \brief Connect to a server
*
* NOTE: Function will block until connection is up or timeout happened.
*
* \param self the connection object
* \param error the error code if an error occurs
* \param hostname the host name or IP address of the server to connect to
@ -243,6 +245,17 @@ IedConnection_setConnectTimeout(IedConnection self, uint32_t timeoutInMs);
void
IedConnection_connect(IedConnection self, IedClientError* error, const char* hostname, int tcpPort);
/**
* \brief Asynchronously connect to a server
*
* \param self the connection object
* \param error the error code if an error occurs
* \param hostname the host name or IP address of the server to connect to
* \param tcpPort the TCP port number of the server to connect to
*/
void
IedConnection_connectAsync(IedConnection self, IedClientError* error, const char* hostname, int tcpPort);
/**
* \brief Abort the connection
*
@ -258,6 +271,22 @@ IedConnection_connect(IedConnection self, IedClientError* error, const char* hos
void
IedConnection_abort(IedConnection self, IedClientError* error);
/**
* \brief Asynchronously abort the connection
*
* This will close the MMS association by sending an ACSE abort message to the server.
* After sending the abort message the connection is closed immediately.
* If the connection is not in "connected" state an IED_ERROR_NOT_CONNECTED error will be reported.
*
* NOTE: This function works asynchronously. The IedConnection object should not be destroyed before the
* connection state changes to IED_STATE_CLOSED.
*
* \param self the connection object
* \param error the error code if an error occurs
*/
void
IedConnection_abortAsync(IedConnection self, IedClientError* error);
/**
* \brief Release the connection
*
@ -273,6 +302,21 @@ IedConnection_abort(IedConnection self, IedClientError* error);
void
IedConnection_release(IedConnection self, IedClientError* error);
/**
* \brief Asynchronously release the connection
*
* This will release the MMS association by sending an MMS conclude message to the server.
* The client can NOT assume the connection to be closed when the function returns, It can
* also fail if the server returns with a negative response. To be sure that the connection
* will be close the close or abort methods should be used. If the connection is not in "connected" state an
* IED_ERROR_NOT_CONNECTED error will be reported.
*
* \param self the connection object
* \param error the error code if an error occurs
*/
void
IedConnection_releaseAsync(IedConnection self, IedClientError* error);
/**
* \brief Close the connection
*
@ -1239,6 +1283,13 @@ void
IedConnection_writeObject(IedConnection self, IedClientError* error, const char* dataAttributeReference, FunctionalConstraint fc,
MmsValue* value);
typedef void
(*IedConnection_WriteObjectHandler) (int invokeId, void* parameter, IedClientError err);
uint32_t
IedConnection_writeObjectAsync(IedConnection self, IedClientError* error, const char* objectReference,
FunctionalConstraint fc, MmsValue* value, IedConnection_WriteObjectHandler handler, void* parameter);
/**
* \brief read a functional constrained data attribute (FCDA) of type boolean
@ -1956,6 +2007,13 @@ MmsVariableSpecification*
IedConnection_getVariableSpecification(IedConnection self, IedClientError* error, const char* dataAttributeReference,
FunctionalConstraint fc);
typedef void
(*IedConnection_GetVariableSpecificationHandler) (int invokeId, void* parameter, IedClientError err, MmsVariableSpecification* spec);
uint32_t
IedConnection_getVariableSpecificationAsync(IedConnection self, IedClientError* error, const char* dataAttributeReference,
FunctionalConstraint fc, IedConnection_GetVariableSpecificationHandler handler, void* parameter);
/** @} */
/**

@ -1779,6 +1779,8 @@ MmsConnection_concludeAsync(MmsConnection self, MmsError* mmsError, MmsConnectio
goto exit_function;
}
*mmsError = MMS_ERROR_NONE;
ByteBuffer* concludeMessage = IsoClientConnection_allocateTransmitBuffer(self->isoClient);
mmsClient_createConcludeRequest(self, concludeMessage);

Loading…
Cancel
Save