From 388337a60d9a158bed1b50715999d3c84d2a0ec8 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Fri, 7 Dec 2018 08:04:47 +0100 Subject: [PATCH] - IEC 61850 client: added support for non-thread mode (IedConnection_createEx, IedConnection_tick) - added example for non-thread mode client and asynchronous API --- CHANGELOG | 21 + .../client_example_setting_groups/Program.cs | 4 +- examples/CMakeLists.txt | 1 + .../iec61850_client_example_async/Makefile | 17 + .../client_example_control.c | 2 +- .../CMakeLists.txt | 17 + .../Makefile | 17 + .../client_example_no_thread.c | 399 ++++++++++++++++++ src/iec61850/client/ied_connection.c | 46 +- src/iec61850/inc/iec61850_client.h | 43 +- 10 files changed, 542 insertions(+), 25 deletions(-) create mode 100644 examples/iec61850_client_example_async/Makefile create mode 100644 examples/iec61850_client_example_no_thread/CMakeLists.txt create mode 100644 examples/iec61850_client_example_no_thread/Makefile create mode 100644 examples/iec61850_client_example_no_thread/client_example_no_thread.c diff --git a/CHANGELOG b/CHANGELOG index 1e9d5045..2379d3fb 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,26 @@ +Changes to version 1.4.0 +------------------------ +- IEC 61850 client: added asynchronous client API (can handle multiple outstanding requests in a single thread) +- IEC 61850 client: added support for non-thread mode (for use with asynchronous API) +- IEC 61850 client: added IedConnection_StateChangedHandler callback that is called for each connection state change (CLOSED, CONNECTING, CONNECTED, CLOSING) +- .NET API: Added support for IedConnection.GetState and StateChangedHandler +- modelviewer: show full hierarchy including sub data objects +- IEC 61850 server: added IedServer_updateCtlModel function to change control model at runtime +- IEC 61850 client: added implementations for functions IedConnection_getLogicalDeviceVariables, IedConnection_getLogicalDeviceDataSets, and IedConnection_getLogicalDeviceDataSetsAsync to address #89 +- .NET API: DataSet implements IDisposable interface, Report/DataSet GetValues methods return now clones of the original native values to prevent GC issues +- .NET API: MmsValue - added Clone method and implemented IDisposable interface +- .NET API: extended MmsValue.ToString method to print arrays and data access errors +- common: MmsVariableSpecification_getChildValue now also accepts "." as separator +- .NET API: ReportControlBlock.GetOwner returns null when no owner available (#79) + + Changes to version 1.3.1 ------------------------ +- GOOSE publisher: fixed problem in payload length calculation +- .NET API: Added method MmsConnection.ReadMultipleVariables +- IEC 61850 client: implemented tissue 1178 client side (select-response+ is +non-NULL) +- SV publisher: fixed RefrTm and SmpSynch handling - IEC 61850 client: improved support for handling segmented reports - .NET API: Added some additional access function to ReportControlBlock - Java SCL parser: added support for timestamp values in "Val" elements diff --git a/dotnet/client_example_setting_groups/Program.cs b/dotnet/client_example_setting_groups/Program.cs index de24ad92..83c60cf6 100644 --- a/dotnet/client_example_setting_groups/Program.cs +++ b/dotnet/client_example_setting_groups/Program.cs @@ -57,8 +57,8 @@ namespace client_examples_setting_groups /* Confirm new setting group values */ con.WriteValue("DEMOPROT/LLN0.SGCB.CnfEdit", FunctionalConstraint.SP, new MmsValue(true)); - /* Read SGCB */ - con.ReadValue("DEMOPROT/LLN0.SGCB", FunctionalConstraint.SP); + /* Read SGCB again */ + sgcbVal = con.ReadValue("DEMOPROT/LLN0.SGCB", FunctionalConstraint.SP); Console.WriteLine("ActSG: {0}",sgcbVal.GetChildValue("ActSG", sgcbVarSpec).ToString()); diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 438f2474..7ab6ae5c 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -23,6 +23,7 @@ add_subdirectory(iec61850_client_example_log) add_subdirectory(iec61850_client_example_array) add_subdirectory(iec61850_client_example_files) add_subdirectory(iec61850_client_example_async) +add_subdirectory(iec61850_client_file_async) if(WIN32) if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/../third_party/winpcap/Lib/wpcap.lib") diff --git a/examples/iec61850_client_example_async/Makefile b/examples/iec61850_client_example_async/Makefile new file mode 100644 index 00000000..95b77003 --- /dev/null +++ b/examples/iec61850_client_example_async/Makefile @@ -0,0 +1,17 @@ +LIBIEC_HOME=../.. + +PROJECT_BINARY_NAME = client_example_async +PROJECT_SOURCES = client_example_async.c + +include $(LIBIEC_HOME)/make/target_system.mk +include $(LIBIEC_HOME)/make/stack_includes.mk + +all: $(PROJECT_BINARY_NAME) + +include $(LIBIEC_HOME)/make/common_targets.mk + +$(PROJECT_BINARY_NAME): $(PROJECT_SOURCES) $(LIB_NAME) + $(CC) $(CFLAGS) $(LDFLAGS) -o $(PROJECT_BINARY_NAME) $(PROJECT_SOURCES) $(INCLUDES) $(LIB_NAME) $(LDLIBS) + +clean: + rm -f $(PROJECT_BINARY_NAME) diff --git a/examples/iec61850_client_example_control/client_example_control.c b/examples/iec61850_client_example_control/client_example_control.c index 02cc78f0..83fba82d 100644 --- a/examples/iec61850_client_example_control/client_example_control.c +++ b/examples/iec61850_client_example_control/client_example_control.c @@ -1,5 +1,5 @@ /* - * client_example3.c + * client_example_control.c * * How to control a device ... intended to be used with server_example_control */ diff --git a/examples/iec61850_client_example_no_thread/CMakeLists.txt b/examples/iec61850_client_example_no_thread/CMakeLists.txt new file mode 100644 index 00000000..1d351b32 --- /dev/null +++ b/examples/iec61850_client_example_no_thread/CMakeLists.txt @@ -0,0 +1,17 @@ + +set(iec61850_client_no_thread_SRCS + client_example_no_thread.c +) + +IF(WIN32) +set_source_files_properties(${iec61850_client_no_thread_SRCS} + PROPERTIES LANGUAGE CXX) +ENDIF(WIN32) + +add_executable(iec61850_client_no_thread + ${iec61850_client_no_thread_SRCS} +) + +target_link_libraries(iec61850_client_no_thread + iec61850 +) diff --git a/examples/iec61850_client_example_no_thread/Makefile b/examples/iec61850_client_example_no_thread/Makefile new file mode 100644 index 00000000..3133e2f7 --- /dev/null +++ b/examples/iec61850_client_example_no_thread/Makefile @@ -0,0 +1,17 @@ +LIBIEC_HOME=../.. + +PROJECT_BINARY_NAME = client_example_no_thread +PROJECT_SOURCES = client_example_no_thread.c + +include $(LIBIEC_HOME)/make/target_system.mk +include $(LIBIEC_HOME)/make/stack_includes.mk + +all: $(PROJECT_BINARY_NAME) + +include $(LIBIEC_HOME)/make/common_targets.mk + +$(PROJECT_BINARY_NAME): $(PROJECT_SOURCES) $(LIB_NAME) + $(CC) $(CFLAGS) $(LDFLAGS) -o $(PROJECT_BINARY_NAME) $(PROJECT_SOURCES) $(INCLUDES) $(LIB_NAME) $(LDLIBS) + +clean: + rm -f $(PROJECT_BINARY_NAME) diff --git a/examples/iec61850_client_example_no_thread/client_example_no_thread.c b/examples/iec61850_client_example_no_thread/client_example_no_thread.c new file mode 100644 index 00000000..d9e87e23 --- /dev/null +++ b/examples/iec61850_client_example_no_thread/client_example_no_thread.c @@ -0,0 +1,399 @@ +/* + * client_example_no_thread.c + * + * Shows how to use IedConnection in non-thread mode together with the asynchronous client API + * + * NOTE: + * - in non-thread mode only asynchronous service functions can be used! + * - the IedConnection_tick function has to be called periodically + * - the asynchronous API returns service results by callback functions + * + * This example is intended to be used with server_example_basic_io. + */ + +#include "iec61850_client.h" + +#include +#include + +#include "hal_thread.h" + +static ClientDataSet clientDataSet = NULL; + +static void +printValue(char* name, MmsValue* value) +{ + char buf[1000]; + + MmsValue_printToBuffer(value, buf, 1000); + + printf("%s: %s\n", name, buf); +} + +/* callback function for IedConnection_readObjectAsync */ +static void +readObjectHandler (uint32_t 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", (char*) parameter, err); + } +} + +/* callback function for IedConnection_readDataSetValuesAsync */ +static void +readDataSetHandler(uint32_t invokeId, void* parameter, IedClientError err, ClientDataSet dataSet) +{ + if (err == IED_ERROR_OK) { + clientDataSet = dataSet; + + printf("Data set has %d entries\n", ClientDataSet_getDataSetSize(dataSet)); + + MmsValue* values = ClientDataSet_getValues(dataSet); + + if (MmsValue_getType(values) == MMS_ARRAY) { + int i; + for (i = 0; i < MmsValue_getArraySize(values); i++) { + printf(" [%i]", i); + printValue("", MmsValue_getElement(values, i)); + } + } + } + else { + printf("Failed to read data set (err=%i)\n", err); + } +} + +static void +writeDataSetHandler(uint32_t invokeId, void* parameter, IedClientError err, LinkedList /* */accessResults) +{ + if (err == IED_ERROR_OK) { + + if (accessResults) { + + int i = 0; + + LinkedList element = LinkedList_getNext(accessResults); + + while (element) { + MmsValue* accessResultValue = LinkedList_getData(element); + + printf(" access-result[%i]", i); + printValue("", accessResultValue); + + + element = LinkedList_getNext(element); + i++; + } + + LinkedList_destroyDeep(accessResults, (LinkedListValueDeleteFunction) MmsValue_delete); + + } + } + else { + printf("Failed to write data set (err=%i)\n", err); + } +} + +static 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 +getControlVariableVarSpecHandler(uint32_t invokeId, void* parameter, IedClientError err, MmsVariableSpecification* spec) +{ + if (err == IED_ERROR_OK) { + + MmsVariableSpecification** ctlVarSpec = (MmsVariableSpecification**)parameter; + + *ctlVarSpec = spec; + } + else { + printf("Failed to get variable specification for object %s (err=%i)\n", (char*) parameter, err); + } +} + +static void +getVarSpecHandler (uint32_t 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", (char*) parameter, err); + } +} + +/* callback function for service functions IedConnection_getLogicalDeviceVariablesAsync, IedConnection_getLogicalDeviceDataSetsAsync */ +static void +getNameListHandler(uint32_t invokeId, void* parameter, IedClientError err, LinkedList nameList, bool moreFollows) +{ + if (err != IED_ERROR_OK) { + printf("Get name list error: %d\n", err); + } + else { + + char* ldName = (char*) parameter; + + LinkedList element = LinkedList_getNext(nameList); + + while (element) { + + char* variableName = (char*) LinkedList_getData(element); + + printf(" %s/%s\n", ldName, variableName); + + element = LinkedList_getNext(element); + } + + LinkedList_destroy(nameList); + + free(ldName); + } +} + +/* callback function for IedConnection_getServerDirectoryAsync */ +static void +getServerDirectoryHandler(uint32_t invokeId, void* parameter, IedClientError err, LinkedList nameList, bool moreFollows) +{ + IedConnection con = (IedConnection) parameter; + + if (err != IED_ERROR_OK) { + printf("Get server directory error: %d\n", err); + } + else { + LinkedList element = LinkedList_getNext(nameList); + + /* Call logical device variables for each logical node */ + while (element) { + + char* ldName = (char*) LinkedList_getData(element); + + printf("LD: %s variables:\n", ldName); + + IedConnection_getLogicalDeviceVariablesAsync(con, &err, ldName, NULL, NULL, getNameListHandler, strdup(ldName)); + + printf("LD: %s data sets:\n", ldName); + + IedConnection_getLogicalDeviceDataSetsAsync(con, &err, ldName, NULL, NULL, getNameListHandler, strdup(ldName)); + + element = LinkedList_getNext(element); + } + + LinkedList_destroy(nameList); + } +} + +static void +controlActionHandler(uint32_t invokeId, void* parameter, IedClientError err, ControlActionType type, bool success) +{ + printf("control: ID: %d type: %i err: %d success: %i\n", invokeId, type, err, success); +} + +static void +stateChangedHandler(void* parameter, IedConnection connection, IedConnectionState newState) +{ + +} + +static void waitWithTick(IedConnection con, int waitMs) +{ + uint64_t startTime = Hal_getTimeInMs(); + + while (Hal_getTimeInMs() < (startTime + (uint64_t) waitMs)) + { + if (IedConnection_tick(con) == true) + Thread_sleep(10); + } +} + +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; + + /* create new IedConnection with non-thread mode */ + IedConnection con = IedConnection_createEx(NULL, false); + + /* track connection state change */ + IedConnection_installStateChangedHandler(con, stateChangedHandler, NULL); + + /* invoke association with server */ + IedConnection_connectAsync(con, &error, hostname, tcpPort); + + if (error == IED_ERROR_OK) { + + bool success = true; + + /* Call IedConnection_tick until state is CONNECTED or CLOSED */ + while (IedConnection_getState(con) != IED_STATE_CONNECTED) { + + if (IedConnection_getState(con) == IED_STATE_CLOSED) { + success = false; + break; + } + + if (IedConnection_tick(con) == true) + Thread_sleep(10); + } + + if (success) { + + IedConnection_getServerDirectoryAsync(con, &error, NULL, NULL, getServerDirectoryHandler, con); + + if (error != IED_ERROR_OK) { + printf("read server directory error %i\n", error); + } + + waitWithTick(con, 1000); + + // ClientReportControlBlock_create() + + 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); + } + + waitWithTick(con, 1000); + + 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); + } + + waitWithTick(con, 1000); + + 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); + } + + waitWithTick(con, 1000); + + IedConnection_readDataSetValuesAsync(con, &error, "simpleIOGenericIO/LLN0.Events", NULL, readDataSetHandler, NULL); + + if (error != IED_ERROR_OK) { + printf("read data set error %i\n", error); + } + + waitWithTick(con, 1000); + + LinkedList values = LinkedList_create(); + LinkedList_add(values, MmsValue_newBoolean(true)); + LinkedList_add(values, MmsValue_newBoolean(false)); + LinkedList_add(values, MmsValue_newBoolean(true)); + LinkedList_add(values, MmsValue_newBoolean(false)); + + IedConnection_writeDataSetValuesAsync(con, &error, "simpleIOGenericIO/LLN0.Events", values, writeDataSetHandler, NULL); + + if (error != IED_ERROR_OK) { + printf("write data set error %i\n", error); + } + + waitWithTick(con, 1000); + + LinkedList_destroyDeep(values, (LinkedListValueDeleteFunction) MmsValue_delete); + + waitWithTick(con, 1000); + + /* Get the varibale specification for the controllable data object by online service */ + MmsVariableSpecification* ctlVarSpec = NULL; + + IedConnection_getVariableSpecificationAsync(con, &error, "simpleIOGenericIO/GGIO1.SPCSO1", IEC61850_FC_CO, + getControlVariableVarSpecHandler, &ctlVarSpec); + + waitWithTick(con, 1000); + + if (ctlVarSpec != NULL) { + + /* ControlObjectClient_create function cannot be used in non-thread mode - use ControlObjectClient_createEx instead */ + ControlObjectClient controlClient = ControlObjectClient_createEx("simpleIOGenericIO/GGIO1.SPCSO1", con, + CONTROL_MODEL_DIRECT_NORMAL, ctlVarSpec); + + if (controlClient != NULL) { + + ControlObjectClient_setOrigin(controlClient, "test1", CONTROL_ORCAT_AUTOMATIC_REMOTE); + + MmsValue* ctlVal = MmsValue_newBoolean(true); + + ControlObjectClient_operateAsync(controlClient, &error, ctlVal, 0, controlActionHandler, NULL); + + waitWithTick(con, 1000); + + MmsValue_delete(ctlVal); + + if (error != IED_ERROR_OK) { + printf("Failed to send operate %i\n", error); + } + + ControlObjectClient_destroy(controlClient); + } + else { + printf("Failed to connect to control object\n"); + } + + MmsVariableSpecification_destroy(ctlVarSpec); + } + + } + + IedConnection_releaseAsync(con, &error); + + if (error != IED_ERROR_OK) { + printf("Release returned error: %d\n", error); + } + else { + + /* Call IedConnection_tick until state is CLOSED */ + while (IedConnection_getState(con) != IED_STATE_CLOSED) { + if (IedConnection_tick(con) == true) + Thread_sleep(10); + } + } + } + else { + printf("Failed to connect to %s:%i\n", hostname, tcpPort); + } + + if (clientDataSet) + ClientDataSet_destroy(clientDataSet); + + IedConnection_destroy(con); +} + + diff --git a/src/iec61850/client/ied_connection.c b/src/iec61850/client/ied_connection.c index e8766229..92c8b201 100644 --- a/src/iec61850/client/ied_connection.c +++ b/src/iec61850/client/ied_connection.c @@ -300,7 +300,7 @@ iedConnection_doesControlObjectMatch(const char* objRef, const char* cntrlObj) if (cntrlObj[i] != '/') return false; - // --> LD is equal + /* --> LD is equal */ i++; @@ -315,7 +315,7 @@ iedConnection_doesControlObjectMatch(const char* objRef, const char* cntrlObj) if (cntrlObj[j++] != '$') return false; - // --> LN is equal + /* --> LN is equal */ if (cntrlObj[j++] != 'C') return false; @@ -324,7 +324,7 @@ iedConnection_doesControlObjectMatch(const char* objRef, const char* cntrlObj) if (cntrlObj[j++] != '$') return false; - // --> FC is ok + /* --> FC is ok */ i++; @@ -366,7 +366,7 @@ doesReportMatchControlObject(char* domainName, char* itemName, const char* objec if (objectRef[i] != '/') return false; - // --> LD is equal + /* --> LD is equal */ i++; int j = 0; @@ -381,7 +381,7 @@ doesReportMatchControlObject(char* domainName, char* itemName, const char* objec if (itemName[j++] != '$') return false; - // --> LN is equal + /* --> LN is equal */ if (itemName[j++] != 'C') return false; @@ -390,7 +390,7 @@ doesReportMatchControlObject(char* domainName, char* itemName, const char* objec if (itemName[j++] != '$') return false; - // --> FC is ok + /* --> FC is ok */ i++; @@ -414,7 +414,7 @@ doesReportMatchControlObject(char* domainName, char* itemName, const char* objec if (itemName[j++] != '$') return false; - // --> object name is equal + /* --> object name is equal */ if (itemName[j++] != 'O') return false; @@ -446,7 +446,7 @@ handleLastApplErrorMessage(IedConnection self, MmsValue* lastApplError) MmsValue* cntrlObj = MmsValue_getElement(lastApplError, 0); MmsValue* error = MmsValue_getElement(lastApplError, 1); - //MmsValue* origin = MmsValue_getElement(lastApplError, 2); + /* MmsValue* origin = MmsValue_getElement(lastApplError, 2); */ MmsValue* ctlNum = MmsValue_getElement(lastApplError, 3); MmsValue* addCause = MmsValue_getElement(lastApplError, 4); if (DEBUG_IED_CLIENT) @@ -563,7 +563,7 @@ mmsConnectionStateChangedHandler(MmsConnection connection, void* parameter, MmsC } static IedConnection -createNewConnectionObject(TLSConfiguration tlsConfig) +createNewConnectionObject(TLSConfiguration tlsConfig, bool useThreads) { IedConnection self = (IedConnection) GLOBAL_CALLOC(1, sizeof(struct sIedConnection)); @@ -572,10 +572,11 @@ createNewConnectionObject(TLSConfiguration tlsConfig) self->logicalDevices = NULL; self->clientControls = LinkedList_create(); - if (tlsConfig) + + if (useThreads) self->connection = MmsConnection_createSecure(tlsConfig); else - self->connection = MmsConnection_create(); + self->connection = MmsConnection_createNonThreaded(tlsConfig); self->state = IED_STATE_CLOSED; @@ -598,13 +599,25 @@ createNewConnectionObject(TLSConfiguration tlsConfig) IedConnection IedConnection_create() { - return createNewConnectionObject(NULL); + return createNewConnectionObject(NULL, true); } IedConnection IedConnection_createWithTlsSupport(TLSConfiguration tlsConfig) { - return createNewConnectionObject(tlsConfig); + return createNewConnectionObject(tlsConfig, true); +} + +IedConnection +IedConnection_createEx(TLSConfiguration tlsConfig, bool useThreads) +{ + return createNewConnectionObject(tlsConfig, useThreads); +} + +bool +IedConnection_tick(IedConnection self) +{ + return MmsConnection_tick(self->connection); } void @@ -988,7 +1001,6 @@ uint32_t IedConnection_getLogicalDeviceDataSetsAsync(IedConnection self, IedClientError* error, const char* ldName, const char* continueAfter, LinkedList result, IedConnection_GetNameListHandler handler, void* parameter) { - //TODO implement IedConnectionOutstandingCall call = iedConnection_allocateOutstandingCall(self); if (call == NULL) { @@ -2536,7 +2548,7 @@ IedConnection_getLogicalNodeVariables(IedConnection self, IedClientError* error, char* logicalNodeName = ldSep + 1; - // search for logical device + /* search for logical device */ LinkedList device = LinkedList_getNext(self->logicalDevices); @@ -2637,7 +2649,7 @@ getDataDirectory(IedConnection self, IedClientError* error, StringUtils_replace(dataNamePart, '.', '$'); - // search for logical device + /* search for logical device */ LinkedList device = LinkedList_getNext(self->logicalDevices); @@ -2806,7 +2818,7 @@ getDataDirectoryByFc(IedConnection self, IedClientError* error, StringUtils_replace(dataNamePart, '.', '$'); - // search for logical device + /* search for logical device */ LinkedList device = LinkedList_getNext(self->logicalDevices); diff --git a/src/iec61850/inc/iec61850_client.h b/src/iec61850/inc/iec61850_client.h index 44ea3440..0bd6f358 100644 --- a/src/iec61850/inc/iec61850_client.h +++ b/src/iec61850/inc/iec61850_client.h @@ -174,21 +174,39 @@ typedef enum { * \brief create a new IedConnection instance * * This function creates a new IedConnection instance that is used to handle a connection to an IED. - * It allocated all required resources. The new connection is in the "idle" state. Before it can be used - * the connect method has to be called. + * It allocated all required resources. The new connection is in the "CLOSED" state. Before it can be used + * the connect method has to be called. The connection will be in non-TLS and thread mode. * * \return the new IedConnection instance */ LIB61850_API IedConnection IedConnection_create(void); +/** + * \brief create a new IedConnection instance (extended version) + * + * This function creates a new IedConnection instance that is used to handle a connection to an IED. + * It allocated all required resources. The new connection is in the "CLOSED" state. Before it can be used + * the \ref IedConnection_connect or \ref IedConnection_connectAsync method has to be called. + * The connection will use TLS when a TLSConfiguration object is provided. The useThread is false the + * IedConnection is in non-thread mode and the IedConnection_tick function has to be called periodically to + * receive messages and perform the house-keeping tasks. + * + * \param tlsConfig the TLS configuration to be used, or NULL for non-TLS connection + * \param useThreads when true, the IedConnection is in thread mode + * + * \return the new IedConnection instance + */ +LIB61850_API IedConnection +IedConnection_createEx(TLSConfiguration tlsConfig, bool useThreads); + /** * \brief create a new IedConnection instance that has support for TLS * * This function creates a new IedConnection instance that is used to handle a connection to an IED. - * It allocated all required resources. The new connection is in the "idle" state. Before it can be used - * the connect method has to be called. The connection will use TLS when a TLSConfiguration object is - * provided. + * It allocated all required resources. The new connection is in the "CLOSED" state. Before it can be used + * the \ref IedConnection_connect or \ref IedConnection_connectAsync method has to be called. + * The connection will use TLS when a TLSConfiguration object is provided. The connection will be in thread mode. * * \param tlsConfig the TLS configuration to be used * @@ -221,6 +239,21 @@ IedConnection_destroy(IedConnection self); LIB61850_API void IedConnection_setConnectTimeout(IedConnection self, uint32_t timeoutInMs); +/** + * \brief Perform MMS message handling and house-keeping tasks (for non-thread mode only) + * + * This function has to be called periodically by the user application in non-thread mode. The return + * value helps to decide when the stack has nothing to do and other tasks can be executed. + * + * NOTE: When using non-thread mode you should NOT use the synchronous (blocking) API functions. The + * synchronous functions will block forever when IedConnection_tick is not called in a separate thread. + * + * \return true when connection is currently waiting and calling thread can be suspended, false means + * connection is busy and the tick function should be called again as soon as possible. + */ +LIB61850_API bool +IedConnection_tick(IedConnection self); + /** * \brief Generic serivce callback handler *