- IEC 61850 client: added support for non-thread mode (IedConnection_createEx, IedConnection_tick)

- added example for non-thread mode client and asynchronous API
pull/93/head
Michael Zillgith 7 years ago
parent 5ed474a44a
commit 388337a60d

@ -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

@ -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());

@ -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")

@ -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)

@ -1,5 +1,5 @@
/*
* client_example3.c
* client_example_control.c
*
* How to control a device ... intended to be used with server_example_control
*/

@ -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
)

@ -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)

@ -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 <stdlib.h>
#include <stdio.h>
#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 /* <MmsValue*> */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);
}

@ -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);

@ -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
*

Loading…
Cancel
Save