diff --git a/CHANGELOG b/CHANGELOG index 35d35461..76d61e07 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -12,6 +12,7 @@ New features and improvements: - Java tools: Support for time stamp Val elements - IedServer: added configuration options to IedServerConfig to make RCB elements read-only (LIB61850-404) - .NET API: Added functions SetReportSetting and GetReportSetting to IedServerConfig (LIB61850-404) +- IED client: added functions IedConnection_setRCBValuesAsync and IedConnection_getRCBValuesAsync (LIB61850-334) - TLS: TLS version 1.3 can be supported when mbedtls 3.6 is used when compiling the library (using mbedtls 2.28 is still possible) - BETA: Support for R-GOOSE and R-SV - BETA: SNTP client code diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 8074ac6a..93aa670f 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -27,6 +27,9 @@ add_subdirectory(iec61850_client_example_array) add_subdirectory(iec61850_client_example_files) add_subdirectory(iec61850_client_example_async) add_subdirectory(iec61850_client_file_async) +add_subdirectory(iec61850_client_example_rcbAsync) +add_subdirectory(iec61850_client_example_ClientGooseControl) +add_subdirectory(iec61850_client_example_ClientGooseControlAsync) if (${BUILD_SNTP_CLIENT_EXAMPLES}) add_subdirectory(sntp_example) diff --git a/examples/Makefile b/examples/Makefile index 87cb1d8e..ebc03899 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -29,6 +29,9 @@ EXAMPLE_DIRS += iec61850_9_2_LE_example EXAMPLE_DIRS += iec61850_sv_client_example EXAMPLE_DIRS += sv_publisher EXAMPLE_DIRS += sv_subscriber +EXAMPLE_DIRS += iec61850_client_example_rcbAsync +EXAMPLE_DIRS += iec61850_client_example_ClientGooseControl +EXAMPLE_DIRS += iec61850_client_example_ClientGooseControlAsync MODEL_DIRS += server_example_simple MODEL_DIRS += server_example_basic_io diff --git a/examples/iec61850_client_example1/client_example1.c b/examples/iec61850_client_example1/client_example1.c index 6c9241f0..0959efe3 100644 --- a/examples/iec61850_client_example1/client_example1.c +++ b/examples/iec61850_client_example1/client_example1.c @@ -152,6 +152,7 @@ close_connection: } else { printf("Failed to connect to %s:%i\n", hostname, tcpPort); + Thread_sleep(60000); } IedConnection_destroy(con); diff --git a/examples/iec61850_client_example_ClientGooseControl/CMakeLists.txt b/examples/iec61850_client_example_ClientGooseControl/CMakeLists.txt new file mode 100644 index 00000000..ac839edc --- /dev/null +++ b/examples/iec61850_client_example_ClientGooseControl/CMakeLists.txt @@ -0,0 +1,17 @@ + +set(iec61850_client_example_ClientGooseControl_SRCS + client_example_ClientGooseControl.c +) + +IF(MSVC) +set_source_files_properties(${iec61850_client_example_ClientGooseControl_SRCS} + PROPERTIES LANGUAGE CXX) +ENDIF(MSVC) + +add_executable(iec61850_client_example_ClientGooseControl + ${iec61850_client_example_ClientGooseControl_SRCS} +) + +target_link_libraries(iec61850_client_example_ClientGooseControl + iec61850 +) \ No newline at end of file diff --git a/examples/iec61850_client_example_ClientGooseControl/Makefile b/examples/iec61850_client_example_ClientGooseControl/Makefile new file mode 100644 index 00000000..e5573a18 --- /dev/null +++ b/examples/iec61850_client_example_ClientGooseControl/Makefile @@ -0,0 +1,17 @@ +LIBIEC_HOME=../.. + +PROJECT_BINARY_NAME = client_example_ClientGooseControl +PROJECT_SOURCES = client_example_ClientGooseControl.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_ClientGooseControl/client_example_ClientGooseControl.c b/examples/iec61850_client_example_ClientGooseControl/client_example_ClientGooseControl.c new file mode 100644 index 00000000..afc5e577 --- /dev/null +++ b/examples/iec61850_client_example_ClientGooseControl/client_example_ClientGooseControl.c @@ -0,0 +1,83 @@ +/* +* client_example_ClientGooseControl.c +* +* This example is intended to be used with server_example_basic_io or server_example_goose. +*/ + +#include "iec61850_client.h" + +#include +#include + +#include "hal_thread.h" + +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_connect(con, &error, hostname, tcpPort); + + if (error == IED_ERROR_OK) + { + /*Read GoCB Values*/ + ClientGooseControlBlock goCB = IedConnection_getGoCBValues(con, &error, "simpleIOGenericIO/LLN0.gcbEvents", NULL); + + bool GoEna = ClientGooseControlBlock_getGoEna(goCB); + printf("GoEna Value: %d\n", GoEna); + + const char* id = ClientGooseControlBlock_getGoID(goCB); + printf("GoID Value: %s\n", id); + + const char* datset = ClientGooseControlBlock_getDatSet(goCB); + printf("GoDatset Value: %s\n", datset); + + /*Update Go CB Values locally*/ + ClientGooseControlBlock_setGoID(goCB, "analog"); + ClientGooseControlBlock_setDatSet(goCB, "simpleIOGenericIO/LLN0$AnalogValues"); + ClientGooseControlBlock_setGoEna(goCB, false); + + /*Update Go CB Values to server (Throws error because only GoEna is writeable)*/ + IedConnection_setGoCBValues(con, &error, goCB, GOCB_ELEMENT_GO_ID | GOCB_ELEMENT_DATSET | GOCB_ELEMENT_GO_ENA, true); + + if (error != IED_ERROR_OK) + printf("Fail to Set Values to Server (code: %i)\n", error); + + /*Test to see if the values were updated correctly on the server*/ + goCB = IedConnection_getGoCBValues(con, &error, "simpleIOGenericIO/LLN0.gcbEvents", NULL); + + bool GoEnaUpdate = ClientGooseControlBlock_getGoEna(goCB); + printf("GoEna Value: %d\n", GoEnaUpdate); + + const char* idUpdate = ClientGooseControlBlock_getGoID(goCB); + printf("GoID Value: %s\n", idUpdate); + + const char* datsetUpdate = ClientGooseControlBlock_getDatSet(goCB); + printf("GoDatset Value: %s\n", datsetUpdate); + + printf("\n"); + + Thread_sleep(50000); + +close_connection: + IedConnection_close(con); + } + else { + printf("Failed to connect to %s:%i\n", hostname, tcpPort); + } + + IedConnection_destroy(con); + + return 0; +} diff --git a/examples/iec61850_client_example_ClientGooseControlAsync/CMakeLists.txt b/examples/iec61850_client_example_ClientGooseControlAsync/CMakeLists.txt new file mode 100644 index 00000000..33286d26 --- /dev/null +++ b/examples/iec61850_client_example_ClientGooseControlAsync/CMakeLists.txt @@ -0,0 +1,17 @@ + +set(iec61850_client_example_ClientGooseControlAsync_SRCS + client_example_ClientGooseControlAsync.c +) + +IF(MSVC) +set_source_files_properties(${iec61850_client_example_ClientGooseControlAsync_SRCS} + PROPERTIES LANGUAGE CXX) +ENDIF(MSVC) + +add_executable(iec61850_client_example_ClientGooseControlAsync + ${iec61850_client_example_ClientGooseControlAsync_SRCS} +) + +target_link_libraries(iec61850_client_example_ClientGooseControlAsync + iec61850 +) \ No newline at end of file diff --git a/examples/iec61850_client_example_ClientGooseControlAsync/Makefile b/examples/iec61850_client_example_ClientGooseControlAsync/Makefile new file mode 100644 index 00000000..79849c00 --- /dev/null +++ b/examples/iec61850_client_example_ClientGooseControlAsync/Makefile @@ -0,0 +1,17 @@ +LIBIEC_HOME=../.. + +PROJECT_BINARY_NAME = client_example_ClientGooseControlAsync +PROJECT_SOURCES = client_example_ClientGooseControlAsync.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_ClientGooseControlAsync/client_example_ClientGooseControlAsync.c b/examples/iec61850_client_example_ClientGooseControlAsync/client_example_ClientGooseControlAsync.c new file mode 100644 index 00000000..7ee70a3e --- /dev/null +++ b/examples/iec61850_client_example_ClientGooseControlAsync/client_example_ClientGooseControlAsync.c @@ -0,0 +1,142 @@ +/* +* client_example_ClientGooseControlAsync.c +* +* This example is intended to be used with server_example_goose. +*/ + +#include "iec61850_client.h" + +#include +#include + +#include "hal_thread.h" + +static ClientGooseControlBlock GOCB = NULL; + +static void +getGoCBValuesHandler(uint32_t invokeId, void* parameter, IedClientError err, ClientGooseControlBlock goCB) +{ + if (err == IED_ERROR_OK) + { + if (goCB) + { + printf("Access to GoCB\n"); + + bool GoEna = ClientGooseControlBlock_getGoEna(goCB); + printf("GoEna Value : % d\n", GoEna); + + const char* id = ClientGooseControlBlock_getGoID(goCB); + printf("GoID Value: %s\n", id); + + const char* datset = ClientGooseControlBlock_getDatSet(goCB); + printf("GoDatset Value: %s\n", datset); + + GOCB = goCB; + } + } + else { + printf("Failed to get GoCV values (err=%i)\n", err); + } +} + +static void +genericServiceHandler(uint32_t invokeId, void* parameter, IedClientError err) +{ + if (err == IED_ERROR_OK) { + printf("Set GoCB Values successful"); + } + else { + printf("Error triggering a report (code: %i)\n", 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) + { + /*Read GoCB Values*/ + IedConnection_getGoCBValuesAsync(con, &error, "simpleIOGenericIO/LLN0.gcbEvents", NULL, getGoCBValuesHandler, NULL); + + if (error != IED_ERROR_OK) { + printf("getGoCBValues service error! %i\n", error); + } + + while (GOCB == NULL) {} + + if (GOCB != NULL) + { + /*Update Go CB Values locally*/ + ClientGooseControlBlock_setGoID(GOCB, "analog"); + ClientGooseControlBlock_setDatSet(GOCB, "simpleIOGenericIO/LLN0$AnalogValues"); + ClientGooseControlBlock_setGoEna(GOCB, false); + + /*Update Go CB Values to server (Throws error because only GoEna is writeable)*/ + IedConnection_setGoCBValuesAsync(con, &error, GOCB, GOCB_ELEMENT_GO_ID | GOCB_ELEMENT_DATSET | GOCB_ELEMENT_GO_ENA, true, genericServiceHandler, NULL); + + if (error != IED_ERROR_OK) { + printf("setGoCBValues service error: %i\n", error); + } + + /*Test to see if the values were updated correctly on the server*/ + IedConnection_getGoCBValuesAsync(con, &error, "simpleIOGenericIO/LLN0.gcbEvents", NULL, getGoCBValuesHandler, NULL); + + ClientGooseControlBlock_destroy(GOCB); + } + } + + Thread_sleep(10000); + + 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); + + return 0; +} diff --git a/examples/iec61850_client_example_rcbAsync/CMakeLists.txt b/examples/iec61850_client_example_rcbAsync/CMakeLists.txt new file mode 100644 index 00000000..c835eec8 --- /dev/null +++ b/examples/iec61850_client_example_rcbAsync/CMakeLists.txt @@ -0,0 +1,17 @@ + +set(iec61850_client_example_rcbAsync_SRCS + client_example_rcbAsync.c +) + +IF(MSVC) +set_source_files_properties(${iec61850_client_example_rcbAsync_SRCS} + PROPERTIES LANGUAGE CXX) +ENDIF(MSVC) + +add_executable(iec61850_client_example_rcbAsync + ${iec61850_client_example_rcbAsync_SRCS} +) + +target_link_libraries(iec61850_client_example_rcbAsync + iec61850 +) diff --git a/examples/iec61850_client_example_rcbAsync/Makefile b/examples/iec61850_client_example_rcbAsync/Makefile new file mode 100644 index 00000000..1c1cbc75 --- /dev/null +++ b/examples/iec61850_client_example_rcbAsync/Makefile @@ -0,0 +1,17 @@ +LIBIEC_HOME=../.. + +PROJECT_BINARY_NAME = client_example_rcbAsync +PROJECT_SOURCES = client_example_rcbAsync.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_rcbAsync/client_example_rcbAsync.c b/examples/iec61850_client_example_rcbAsync/client_example_rcbAsync.c new file mode 100644 index 00000000..d0defae5 --- /dev/null +++ b/examples/iec61850_client_example_rcbAsync/client_example_rcbAsync.c @@ -0,0 +1,226 @@ +/* + * client_example_rcbAsync.c + * + * This example is intended to be used with server_example_basic_io or server_example_goose. + */ + +#include "iec61850_client.h" + +#include +#include + +#include "hal_thread.h" + +static ClientDataSet clientDataSet = NULL; +static ClientReportControlBlock RCB = NULL; + +static void +printValue(char* name, MmsValue* value) +{ + char buf[1000]; + + MmsValue_printToBuffer(value, buf, 1000); + printf("%s: %s\n", name, buf); +} + +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); + } +} + +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); + } +} + +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 +getRCBValuesHandler(uint32_t invokeId, void* parameter, IedClientError err, ClientReportControlBlock rcb) +{ + if (err == IED_ERROR_OK) { + + if (rcb) { + bool rptEna = ClientReportControlBlock_getRptEna(rcb); + printf("RptEna = %i\n", rptEna); + + const char* RptId = ClientReportControlBlock_getRptId(rcb); + printf("RptID = %s\n", RptId); + + RCB = rcb; + } + } + else{ + printf("Failed to get RCB Values (err=%i)\n", err); + } +}; + +static void +genericServiceHandler(uint32_t invokeId, void* parameter, IedClientError err) +{ + if (err == IED_ERROR_OK) { + printf("Success triggering a GI report\n"); + } + else { + printf("Error triggering a GI report (code: %i)\n", 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) { + + /*read measurement value from server*/ + IedConnection_readObjectAsync(con, &error, "simpleIOGenericIO/GGIO1.AnIn1.mag.f", IEC61850_FC_MX, readObjectHandler, "simpleIOGenericIO/GGIO1.AnIn2.mag.f"); + + if (error != IED_ERROR_OK) { + printf("read object error %i\n", error); + } + + /*read data set*/ + IedConnection_readDataSetValuesAsync(con, &error, "simpleIOGenericIO/LLN0.Events", NULL, readDataSetHandler, NULL); + + if (error != IED_ERROR_OK) { + printf("read data set error %i\n", error); + } + + /* Read RCB values from server*/ + IedConnection_getRCBValuesAsync(con, &error, "simpleIOGenericIO/LLN0.RP.EventsRCB01", NULL, getRCBValuesHandler, NULL); + + if (error != IED_ERROR_OK) { + printf("getRCBValues service error! %i\n", error); + } + + while (RCB == NULL) {} + + if (RCB != NULL) { + + /*Set RCB Values locally*/ + ClientReportControlBlock_setResv(RCB, true); + ClientReportControlBlock_setTrgOps(RCB, TRG_OPT_DATA_CHANGED | TRG_OPT_QUALITY_CHANGED | TRG_OPT_GI); + ClientReportControlBlock_setDataSetReference(RCB, "simpleIOGenericIO/LLN0$Events"); /* NOTE the "$" instead of "." ! */ + ClientReportControlBlock_setRptEna(RCB, true); + ClientReportControlBlock_setGI(RCB, true); + + /*Set RCB Values to server*/ + IedConnection_setRCBValuesAsync(con, &error, RCB, RCB_ELEMENT_RESV | RCB_ELEMENT_DATSET | RCB_ELEMENT_TRG_OPS | RCB_ELEMENT_RPT_ENA | RCB_ELEMENT_GI, true, genericServiceHandler, NULL); + + if (error != IED_ERROR_OK) { + printf("setRCBValues service error!\n"); + } + + Thread_sleep(1000); + + /*Trigger GI Report*/ + ClientReportControlBlock_setGI(RCB, true); + IedConnection_setRCBValuesAsync(con, &error, RCB, RCB_ELEMENT_GI, true, genericServiceHandler, NULL); + + if (error != IED_ERROR_OK) + printf("Error triggering a GI report (code: %i)\n", error); + + Thread_sleep(60000); + + /*Disable Reporting*/ + ClientReportControlBlock_setRptEna(RCB, false); + IedConnection_setRCBValuesAsync(con, &error, RCB, RCB_ELEMENT_RPT_ENA, true, genericServiceHandler, NULL); + + if (error != IED_ERROR_OK) + printf("disable reporting failed (code: %i)\n", error); + + ClientDataSet_destroy(clientDataSet); + ClientReportControlBlock_destroy(RCB); + + } + } + + Thread_sleep(50000); + + 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); + return 0; +} \ No newline at end of file diff --git a/src/iec61850/client/client_goose_control.c b/src/iec61850/client/client_goose_control.c index 467af2d2..3a31b687 100644 --- a/src/iec61850/client/client_goose_control.c +++ b/src/iec61850/client/client_goose_control.c @@ -3,7 +3,7 @@ * * Implementation of the ClientReportControlBlock class * - * Copyright 2014 Michael Zillgith + * Copyright 2014-2024 Michael Zillgith * * This file is part of libIEC61850. * @@ -31,7 +31,8 @@ #include "libiec61850_platform_includes.h" -struct sClientGooseControlBlock { +struct sClientGooseControlBlock +{ char* objectReference; MmsValue* goEna; MmsValue* goID; @@ -49,7 +50,10 @@ ClientGooseControlBlock_create(const char* objectReference) { ClientGooseControlBlock self = (ClientGooseControlBlock) GLOBAL_CALLOC(1, sizeof(struct sClientGooseControlBlock)); - self->objectReference = StringUtils_copyString(objectReference); + if (self) + { + self->objectReference = StringUtils_copyString(objectReference); + } return self; } @@ -57,19 +61,22 @@ ClientGooseControlBlock_create(const char* objectReference) void ClientGooseControlBlock_destroy(ClientGooseControlBlock self) { - GLOBAL_FREEMEM(self->objectReference); - - MmsValue_delete(self->goEna); - MmsValue_delete(self->goID); - MmsValue_delete(self->datSet); - MmsValue_delete(self->confRev); - MmsValue_delete(self->ndsCom); - MmsValue_delete(self->dstAddress); - MmsValue_delete(self->minTime); - MmsValue_delete(self->maxTime); - MmsValue_delete(self->fixedOffs); - - GLOBAL_FREEMEM(self); + if (self) + { + GLOBAL_FREEMEM(self->objectReference); + + MmsValue_delete(self->goEna); + MmsValue_delete(self->goID); + MmsValue_delete(self->datSet); + MmsValue_delete(self->confRev); + MmsValue_delete(self->ndsCom); + MmsValue_delete(self->dstAddress); + MmsValue_delete(self->minTime); + MmsValue_delete(self->maxTime); + MmsValue_delete(self->fixedOffs); + + GLOBAL_FREEMEM(self); + } } bool @@ -172,13 +179,17 @@ ClientGooseControlBlock_getFixedOffs(ClientGooseControlBlock self) } static MmsValue* -newEmptyPhyCommAddress(void) { +newEmptyPhyCommAddress(void) +{ MmsValue* self = MmsValue_createEmptyStructure(4); - MmsValue_setElement(self, 0, MmsValue_newOctetString(6, 6)); - MmsValue_setElement(self, 1, MmsValue_newUnsigned(8)); - MmsValue_setElement(self, 2, MmsValue_newUnsigned(16)); - MmsValue_setElement(self, 3, MmsValue_newUnsigned(16)); + if (self) + { + MmsValue_setElement(self, 0, MmsValue_newOctetString(6, 6)); + MmsValue_setElement(self, 1, MmsValue_newUnsigned(8)); + MmsValue_setElement(self, 2, MmsValue_newUnsigned(16)); + MmsValue_setElement(self, 3, MmsValue_newUnsigned(16)); + } return self; } @@ -191,24 +202,28 @@ ClientGooseControlBlock_getDstAddress(ClientGooseControlBlock self) if (self->dstAddress == NULL) goto exit_error; - if (MmsValue_getType(self->dstAddress) != MMS_STRUCTURE) { + if (MmsValue_getType(self->dstAddress) != MMS_STRUCTURE) + { if (DEBUG_IED_CLIENT) printf("IED_CLIENT: GoCB - addr has wrong type\n"); goto exit_error; } - if (MmsValue_getArraySize(self->dstAddress) != 4) { + if (MmsValue_getArraySize(self->dstAddress) != 4) + { if (DEBUG_IED_CLIENT) printf("IED_CLIENT: GoCB - addr has wrong type\n"); goto exit_error; } MmsValue* addr = MmsValue_getElement(self->dstAddress, 0); - if (MmsValue_getType(addr) != MMS_OCTET_STRING) { + if (MmsValue_getType(addr) != MMS_OCTET_STRING) + { if (DEBUG_IED_CLIENT) printf("IED_CLIENT: GoCB - addr has wrong type\n"); goto exit_error; } - if (MmsValue_getOctetStringSize(addr) != 6) { + if (MmsValue_getOctetStringSize(addr) != 6) + { if (DEBUG_IED_CLIENT) printf("IED_CLIENT: GoCB - addr has wrong size\n"); goto exit_error; } @@ -219,7 +234,8 @@ ClientGooseControlBlock_getDstAddress(ClientGooseControlBlock self) MmsValue* prio = MmsValue_getElement(self->dstAddress, 1); - if (MmsValue_getType(prio) != MMS_UNSIGNED) { + if (MmsValue_getType(prio) != MMS_UNSIGNED) + { if (DEBUG_IED_CLIENT) printf("IED_CLIENT: GoCB - prio has wrong type\n"); goto exit_error; } @@ -228,7 +244,8 @@ ClientGooseControlBlock_getDstAddress(ClientGooseControlBlock self) MmsValue* vid = MmsValue_getElement(self->dstAddress, 2); - if (MmsValue_getType(vid) != MMS_UNSIGNED) { + if (MmsValue_getType(vid) != MMS_UNSIGNED) + { if (DEBUG_IED_CLIENT) printf("IED_CLIENT: GoCB - vid has wrong type\n"); goto exit_error; } @@ -237,7 +254,8 @@ ClientGooseControlBlock_getDstAddress(ClientGooseControlBlock self) MmsValue* appID = MmsValue_getElement(self->dstAddress, 3); - if (MmsValue_getType(appID) != MMS_UNSIGNED) { + if (MmsValue_getType(appID) != MMS_UNSIGNED) + { if (DEBUG_IED_CLIENT) printf("IED_CLIENT: GoCB - appID has wrong type\n"); goto exit_error; } @@ -254,8 +272,8 @@ ClientGooseControlBlock_setDstAddress(ClientGooseControlBlock self, PhyComAddres if (self->dstAddress == NULL) self->dstAddress = newEmptyPhyCommAddress(); - if (self->dstAddress) { - + if (self->dstAddress) + { MmsValue* addr = MmsValue_getElement(self->dstAddress, 0); MmsValue_setOctetString(addr, value.dstAddress, 6); @@ -364,7 +382,8 @@ private_ClientGooseControlBlock_updateValues(ClientGooseControlBlock self, MmsVa { int elementCount = MmsValue_getArraySize(values); - if (elementCount > 5) { + if (elementCount > 5) + { updateOrClone(&(self->goEna), values, 0); updateOrClone(&(self->goID), values, 1); updateOrClone(&(self->datSet), values, 2); @@ -398,7 +417,8 @@ IedConnection_getGoCBValues(IedConnection self, IedClientError* error, const cha char domainId[65]; char itemId[130]; - if (MmsMapping_getMmsDomainFromObjectReference(goCBReference, domainId) == NULL) { + if (MmsMapping_getMmsDomainFromObjectReference(goCBReference, domainId) == NULL) + { *error = IED_ERROR_OBJECT_REFERENCE_INVALID; return NULL; } @@ -409,7 +429,8 @@ IedConnection_getGoCBValues(IedConnection self, IedClientError* error, const cha const char* separator = strchr(itemIdStart, '.'); - if (separator == NULL) { + if (separator == NULL) + { *error = IED_ERROR_OBJECT_REFERENCE_INVALID; return NULL; } @@ -431,18 +452,21 @@ IedConnection_getGoCBValues(IedConnection self, IedClientError* error, const cha MmsValue* goCB = MmsConnection_readVariable(self->connection, &mmsError, domainId, itemId); - if (mmsError != MMS_ERROR_NONE) { + if (mmsError != MMS_ERROR_NONE) + { *error = iedConnection_mapMmsErrorToIedError(mmsError); return NULL; } - if (goCB == NULL) { + if (goCB == NULL) + { *error = IED_ERROR_OBJECT_DOES_NOT_EXIST; return NULL; } - if (MmsValue_getType(goCB) != MMS_STRUCTURE) { + if (MmsValue_getType(goCB) != MMS_STRUCTURE) + { if (DEBUG_IED_CLIENT) printf("DEBUG_IED_CLIENT: getRCBValues returned wrong type!\n"); @@ -465,6 +489,147 @@ IedConnection_getGoCBValues(IedConnection self, IedClientError* error, const cha return returnGoCB; } +static void +readObjectHandlerInternal(uint32_t invokeId, void* parameter, MmsError err, MmsValue* value) +{ + IedConnection self = (IedConnection) parameter; + + IedConnectionOutstandingCall call = iedConnection_lookupOutstandingCall(self, invokeId); + + if (call) + { + IedConnection_GetGoCBValuesHandler handler = (IedConnection_GetGoCBValuesHandler) call->callback; + ClientGooseControlBlock updateGoCB = (ClientGooseControlBlock) call->specificParameter; + char* goCBReference = (char*) call->specificParameter2.pointer; + + if (err != MMS_ERROR_NONE) + { + handler(invokeId, call->callbackParameter, iedConnection_mapMmsErrorToIedError(err), NULL); + } + else + { + if (value == NULL) + { + handler(invokeId, call->callbackParameter, IED_ERROR_OBJECT_DOES_NOT_EXIST, NULL); + } + else + { + if (MmsValue_getType(value) == MMS_DATA_ACCESS_ERROR) + { + if (DEBUG_IED_CLIENT) + printf("DEBUG_IED_CLIENT: getGoCBValues returned data-access-error!\n"); + + handler(invokeId, call->callbackParameter, iedConnection_mapDataAccessErrorToIedError(MmsValue_getDataAccessError(value)), NULL); + } + else + { + ClientGooseControlBlock returnGoCB = updateGoCB; + + if (returnGoCB == NULL) + returnGoCB = ClientGooseControlBlock_create(goCBReference); + + if (private_ClientGooseControlBlock_updateValues(returnGoCB, value)) + { + handler(invokeId, call->callbackParameter, IED_ERROR_OK, returnGoCB); + } + else + { + if (DEBUG_IED_CLIENT) + printf("DEBUG_IED_CLIENT: getGoCBValues returned wrong type!\n"); + + handler(invokeId, call->callbackParameter, IED_ERROR_TYPE_INCONSISTENT, NULL); + + if (updateGoCB == NULL) + ClientGooseControlBlock_destroy(returnGoCB); + } + } + + MmsValue_delete(value); + } + } + + GLOBAL_FREEMEM(goCBReference); + + iedConnection_releaseOutstandingCall(self, call); + } + else + { + if (DEBUG_IED_CLIENT) + printf("IED_CLIENT: internal error - no matching outstanding call!\n"); + } +} + +uint32_t +IedConnection_getGoCBValuesAsync(IedConnection self, IedClientError* error, const char* goCBReference, ClientGooseControlBlock updateGoCB, + IedConnection_GetGoCBValuesHandler handler, void* parameter) +{ + *error = IED_ERROR_OK; + + char domainId[65]; + char itemId[130]; + + if (MmsMapping_getMmsDomainFromObjectReference(goCBReference, domainId) == NULL) + { + *error = IED_ERROR_OBJECT_REFERENCE_INVALID; + return 0; + } + + int domainIdSize = strlen(domainId); + + const char* itemIdStart = goCBReference + domainIdSize + 1; + + const char* separator = strchr(itemIdStart, '.'); + + if (separator == NULL) + { + *error = IED_ERROR_OBJECT_REFERENCE_INVALID; + return 0; + } + + int separatorOffset = separator - itemIdStart; + + memcpy(itemId, itemIdStart, separatorOffset); + + itemId[separatorOffset] = '$'; + itemId[separatorOffset + 1] = 'G'; + itemId[separatorOffset + 2] = 'O'; + itemId[separatorOffset + 3] = '$'; + itemId[separatorOffset + 4] = 0; + + StringUtils_appendString(itemId, 130, separator + 1); + + IedConnectionOutstandingCall call = iedConnection_allocateOutstandingCall(self); + + if (call == NULL) + { + *error = IED_ERROR_OUTSTANDING_CALL_LIMIT_REACHED; + return 0; + } + + call->callback = handler; + call->callbackParameter = parameter; + call->specificParameter = updateGoCB; + call->specificParameter2.pointer = StringUtils_copyString(goCBReference); + + if (DEBUG_IED_CLIENT) + printf("DEBUG_IED_CLIENT: readGoCBValues for %s\n", goCBReference); + + MmsError err = MMS_ERROR_NONE; + + MmsConnection_readVariableAsync(self->connection, &(call->invokeId), &err, domainId, itemId, readObjectHandlerInternal, self); + + *error = iedConnection_mapMmsErrorToIedError(err); + + if (err != MMS_ERROR_NONE) + { + GLOBAL_FREEMEM(call->specificParameter2.pointer); + iedConnection_releaseOutstandingCall(self, call); + return 0; + } + + return call->invokeId; +} + void IedConnection_setGoCBValues(IedConnection self, IedClientError* error, ClientGooseControlBlock goCB, uint32_t parametersMask, bool singleRequest) @@ -476,7 +641,8 @@ IedConnection_setGoCBValues(IedConnection self, IedClientError* error, ClientGoo char domainId[65]; char itemId[130]; - if (MmsMapping_getMmsDomainFromObjectReference(goCB->objectReference, domainId) == NULL) { + if (MmsMapping_getMmsDomainFromObjectReference(goCB->objectReference, domainId) == NULL) + { *error = IED_ERROR_OBJECT_REFERENCE_INVALID; return; } @@ -485,7 +651,8 @@ IedConnection_setGoCBValues(IedConnection self, IedClientError* error, ClientGoo char* separator = strchr(itemIdStart, '.'); - if (separator == NULL) { + if (separator == NULL) + { *error = IED_ERROR_OBJECT_REFERENCE_INVALID; return; } @@ -512,7 +679,8 @@ IedConnection_setGoCBValues(IedConnection self, IedClientError* error, ClientGoo LinkedList values = LinkedList_create(); /* add rGoEna as last element */ - if (parametersMask & GOCB_ELEMENT_GO_ID) { + if (parametersMask & GOCB_ELEMENT_GO_ID) + { StringUtils_appendString(itemId, 130, "$GoID"); LinkedList_add(itemIds, StringUtils_copyString(itemId)); @@ -521,7 +689,8 @@ IedConnection_setGoCBValues(IedConnection self, IedClientError* error, ClientGoo itemId[itemIdLen] = 0; } - if (parametersMask & GOCB_ELEMENT_DATSET) { + if (parametersMask & GOCB_ELEMENT_DATSET) + { StringUtils_appendString(itemId, 130, "$DatSet"); LinkedList_add(itemIds, StringUtils_copyString(itemId)); @@ -530,7 +699,8 @@ IedConnection_setGoCBValues(IedConnection self, IedClientError* error, ClientGoo itemId[itemIdLen] = 0; } - if (parametersMask & GOCB_ELEMENT_CONF_REV) { + if (parametersMask & GOCB_ELEMENT_CONF_REV) + { StringUtils_appendString(itemId, 130, "$ConfRev"); LinkedList_add(itemIds, StringUtils_copyString(itemId)); @@ -539,7 +709,8 @@ IedConnection_setGoCBValues(IedConnection self, IedClientError* error, ClientGoo itemId[itemIdLen] = 0; } - if (parametersMask & GOCB_ELEMENT_NDS_COMM) { + if (parametersMask & GOCB_ELEMENT_NDS_COMM) + { StringUtils_appendString(itemId, 130, "$NdsCom"); LinkedList_add(itemIds, StringUtils_copyString(itemId)); @@ -548,7 +719,8 @@ IedConnection_setGoCBValues(IedConnection self, IedClientError* error, ClientGoo itemId[itemIdLen] = 0; } - if (parametersMask & GOCB_ELEMENT_DST_ADDRESS) { + if (parametersMask & GOCB_ELEMENT_DST_ADDRESS) + { StringUtils_appendString(itemId, 130, "$DstAddress"); LinkedList_add(itemIds, StringUtils_copyString(itemId)); @@ -557,7 +729,8 @@ IedConnection_setGoCBValues(IedConnection self, IedClientError* error, ClientGoo itemId[itemIdLen] = 0; } - if (parametersMask & GOCB_ELEMENT_MIN_TIME) { + if (parametersMask & GOCB_ELEMENT_MIN_TIME) + { StringUtils_appendString(itemId, 130, "$MinTime"); LinkedList_add(itemIds, StringUtils_copyString(itemId)); @@ -566,7 +739,8 @@ IedConnection_setGoCBValues(IedConnection self, IedClientError* error, ClientGoo itemId[itemIdLen] = 0; } - if (parametersMask & GOCB_ELEMENT_MAX_TIME) { + if (parametersMask & GOCB_ELEMENT_MAX_TIME) + { StringUtils_appendString(itemId, 130, "$MaxTime"); LinkedList_add(itemIds, StringUtils_copyString(itemId)); @@ -575,7 +749,8 @@ IedConnection_setGoCBValues(IedConnection self, IedClientError* error, ClientGoo itemId[itemIdLen] = 0; } - if (parametersMask & GOCB_ELEMENT_FIXED_OFFS) { + if (parametersMask & GOCB_ELEMENT_FIXED_OFFS) + { StringUtils_appendString(itemId, 130, "$FixedOffs"); LinkedList_add(itemIds, StringUtils_copyString(itemId)); @@ -584,7 +759,8 @@ IedConnection_setGoCBValues(IedConnection self, IedClientError* error, ClientGoo itemId[itemIdLen] = 0; } - if (parametersMask & GOCB_ELEMENT_GO_ENA) { + if (parametersMask & GOCB_ELEMENT_GO_ENA) + { StringUtils_appendString(itemId, 130, "$GoEna"); LinkedList_add(itemIds, StringUtils_copyString(itemId)); @@ -593,23 +769,26 @@ IedConnection_setGoCBValues(IedConnection self, IedClientError* error, ClientGoo itemId[itemIdLen] = 0; } - if (singleRequest) { + if (singleRequest) + { LinkedList accessResults = NULL; *error = IED_ERROR_OK; MmsConnection_writeMultipleVariables(self->connection, &mmsError, domainId, itemIds, values, &accessResults); - if (accessResults != NULL) { + if (accessResults) + { LinkedList element = LinkedList_getNext(accessResults); - while (element != NULL) { + while (element) + { MmsValue* accessResult = (MmsValue*) element->data; MmsDataAccessError resErr = MmsValue_getDataAccessError(accessResult); - if (MmsValue_getDataAccessError(accessResult) != DATA_ACCESS_ERROR_SUCCESS) { - + if (MmsValue_getDataAccessError(accessResult) != DATA_ACCESS_ERROR_SUCCESS) + { *error = iedConnection_mapDataAccessErrorToIedError(resErr); break; @@ -623,11 +802,13 @@ IedConnection_setGoCBValues(IedConnection self, IedClientError* error, ClientGoo goto exit_function; } - else { + else + { LinkedList itemIdElement = LinkedList_getNext(itemIds); LinkedList valueElement = LinkedList_getNext(values); - while (itemIdElement != NULL) { + while (itemIdElement) + { char* rcbItemId = (char*) itemIdElement->data; MmsValue* value = (MmsValue*) valueElement->data; @@ -649,3 +830,345 @@ exit_function: LinkedList_destroyStatic(values); } +static void +writeMultipleVariablesHandler(uint32_t invokeId, void* parameter, MmsError mmsError, LinkedList /* */ accessResults) +{ + IedConnection self = (IedConnection)parameter; + + IedConnectionOutstandingCall call = iedConnection_lookupOutstandingCall(self, invokeId); + + if (call) + { + IedConnection_GenericServiceHandler handler = (IedConnection_GenericServiceHandler) call->callback; + + if (accessResults) + { + IedClientError error = IED_ERROR_OK; + + LinkedList accessResult = LinkedList_getNext(accessResults); + + while (accessResult) + { + MmsValue* dataAccessError = (MmsValue*) accessResult->data; + + if (MmsValue_getDataAccessError(dataAccessError) != DATA_ACCESS_ERROR_SUCCESS) + { + error = iedConnection_mapDataAccessErrorToIedError(MmsValue_getDataAccessError(dataAccessError)); + break; + } + + accessResult = LinkedList_getNext(accessResult); + } + + LinkedList_destroyDeep(accessResults, (LinkedListValueDeleteFunction)MmsValue_delete); + + handler(invokeId, call->callbackParameter, error); + } + else + { + handler(invokeId, call->callbackParameter, iedConnection_mapMmsErrorToIedError(mmsError)); + } + + iedConnection_releaseOutstandingCall(self, call); + } + else + { + if (DEBUG_IED_CLIENT) + printf("IED_CLIENT: internal error - no matching outstanding call with invoke ID: %u!\n", invokeId); + } +} + +struct sWriteGoCBVariablesParameter +{ + LinkedList itemIds; + LinkedList values; + LinkedList currentItemId; + LinkedList currentValue; + char* domainId; + uint32_t originalInvokeId; +}; + +static void +releaseWriteCall(IedConnection self, IedConnectionOutstandingCall call, struct sWriteGoCBVariablesParameter* param) +{ + GLOBAL_FREEMEM(param->domainId); + + LinkedList_destroy(param->itemIds); + LinkedList_destroyStatic(param->values); + + GLOBAL_FREEMEM(param); + + iedConnection_releaseOutstandingCall(self, call); +} + +static void +writeVariableHandler(uint32_t invokeId, void* parameter, MmsError mmsError, MmsDataAccessError accessError) +{ + IedConnection self = (IedConnection) parameter; + + IedConnectionOutstandingCall call = iedConnection_lookupOutstandingCall(self, invokeId); + + if (call) + { + IedConnection_GenericServiceHandler handler = (IedConnection_GenericServiceHandler) call->callback; + + struct sWriteGoCBVariablesParameter* param = (struct sWriteGoCBVariablesParameter*) call->specificParameter2.pointer; + + if ((mmsError != MMS_ERROR_NONE) || (accessError != DATA_ACCESS_ERROR_SUCCESS)) + { + IedClientError err; + + if (mmsError != MMS_ERROR_NONE) + err = iedConnection_mapMmsErrorToIedError(mmsError); + else + err = iedConnection_mapDataAccessErrorToIedError(accessError); + + handler(param->originalInvokeId, call->callbackParameter, err); + + releaseWriteCall(self, call, param); + } + + param->currentItemId = LinkedList_getNext(param->currentItemId); + + if (param->currentItemId == NULL) + { + handler(param->originalInvokeId, call->callbackParameter, IED_ERROR_OK); + + releaseWriteCall(self, call, param); + } + else + { + param->currentValue = LinkedList_getNext(param->currentValue); + + char* itemId = (char*) LinkedList_getData(param->currentItemId); + MmsValue* value = (MmsValue*)LinkedList_getData(param->currentValue); + + MmsError writeError; + + MmsConnection_writeVariableAsync(self->connection, &(call->invokeId), &writeError, param->domainId, itemId, value, writeVariableHandler, self); + + if (writeError != MMS_ERROR_NONE) + { + handler(param->originalInvokeId, call->callbackParameter, iedConnection_mapMmsErrorToIedError(writeError)); + + releaseWriteCall(self, call, param); + } + } + } + else + { + if (DEBUG_IED_CLIENT) + printf("IED_CLIENT: internal error - no matching outstanding call!\n"); + } +} + +uint32_t +IedConnection_setGoCBValuesAsync(IedConnection self, IedClientError* error, ClientGooseControlBlock goCB, + uint32_t parametersMask, bool singleRequest, IedConnection_GenericServiceHandler handler, void* parameter) +{ + *error = IED_ERROR_OK; + + uint32_t invokeId = 0; + + char domainId[65]; + char itemId[130]; + + if (MmsMapping_getMmsDomainFromObjectReference(goCB->objectReference, domainId) == NULL) + { + *error = IED_ERROR_OBJECT_REFERENCE_INVALID;; + } + + char* itemIdStart = goCB->objectReference + strlen(domainId) + 1; + + char* separator = strchr(itemIdStart, '.'); + + if (separator == NULL) + { + *error = IED_ERROR_OBJECT_REFERENCE_INVALID; + goto exit_function; + } + + int separatorOffset = separator - itemIdStart; + + memcpy(itemId, itemIdStart, separatorOffset); + + itemId[separatorOffset] = '$'; + itemId[separatorOffset + 1] = 'G'; + itemId[separatorOffset + 2] = 'O'; + itemId[separatorOffset + 3] = '$'; + itemId[separatorOffset + 4] = 0; + + StringUtils_appendString(itemId, 130, separator + 1); + + if (DEBUG_IED_CLIENT) + printf("DEBUG_IED_CLIENT: setGoCBValues for %s\n", goCB->objectReference); + + int itemIdLen = strlen(itemId); + + /* create the list of requested itemIds references */ + LinkedList itemIds = LinkedList_create(); + LinkedList values = LinkedList_create(); + + /* add rGoEna as last element */ + if (parametersMask & GOCB_ELEMENT_GO_ID) + { + StringUtils_appendString(itemId, 130, "$GoID"); + + LinkedList_add(itemIds, StringUtils_copyString(itemId)); + LinkedList_add(values, goCB->goID); + + itemId[itemIdLen] = 0; + } + + if (parametersMask & GOCB_ELEMENT_DATSET) + { + StringUtils_appendString(itemId, 130, "$DatSet"); + + LinkedList_add(itemIds, StringUtils_copyString(itemId)); + LinkedList_add(values, goCB->datSet); + + itemId[itemIdLen] = 0; + } + + if (parametersMask & GOCB_ELEMENT_CONF_REV) + { + StringUtils_appendString(itemId, 130, "$ConfRev"); + + LinkedList_add(itemIds, StringUtils_copyString(itemId)); + LinkedList_add(values, goCB->confRev); + + itemId[itemIdLen] = 0; + } + + if (parametersMask & GOCB_ELEMENT_NDS_COMM) + { + StringUtils_appendString(itemId, 130, "$NdsCom"); + + LinkedList_add(itemIds, StringUtils_copyString(itemId)); + LinkedList_add(values, goCB->ndsCom); + + itemId[itemIdLen] = 0; + } + + if (parametersMask & GOCB_ELEMENT_DST_ADDRESS) + { + StringUtils_appendString(itemId, 130, "$DstAddress"); + + LinkedList_add(itemIds, StringUtils_copyString(itemId)); + LinkedList_add(values, goCB->dstAddress); + + itemId[itemIdLen] = 0; + } + + if (parametersMask & GOCB_ELEMENT_MIN_TIME) + { + StringUtils_appendString(itemId, 130, "$MinTime"); + + LinkedList_add(itemIds, StringUtils_copyString(itemId)); + LinkedList_add(values, goCB->minTime); + + itemId[itemIdLen] = 0; + } + + if (parametersMask & GOCB_ELEMENT_MAX_TIME) + { + StringUtils_appendString(itemId, 130, "$MaxTime"); + + LinkedList_add(itemIds, StringUtils_copyString(itemId)); + LinkedList_add(values, goCB->maxTime); + + itemId[itemIdLen] = 0; + } + + if (parametersMask & GOCB_ELEMENT_FIXED_OFFS) + { + StringUtils_appendString(itemId, 130, "$FixedOffs"); + + LinkedList_add(itemIds, StringUtils_copyString(itemId)); + LinkedList_add(values, goCB->fixedOffs); + + itemId[itemIdLen] = 0; + } + + if (parametersMask & GOCB_ELEMENT_GO_ENA) + { + StringUtils_appendString(itemId, 130, "$GoEna"); + + LinkedList_add(itemIds, StringUtils_copyString(itemId)); + LinkedList_add(values, goCB->goEna); + + itemId[itemIdLen] = 0; + } + + IedConnectionOutstandingCall call = iedConnection_allocateOutstandingCall(self); + + if (call == NULL) + { + *error = IED_ERROR_OUTSTANDING_CALL_LIMIT_REACHED; + goto exit_function; + } + + call->callback = handler; + call->callbackParameter = parameter; + call->specificParameter = goCB; + + MmsError err; + + if (singleRequest) + { + MmsConnection_writeMultipleVariablesAsync(self->connection, &(call->invokeId), &err, domainId, itemIds, values, writeMultipleVariablesHandler, self); + + *error = iedConnection_mapMmsErrorToIedError(err); + + if (err != MMS_ERROR_NONE) + { + iedConnection_releaseOutstandingCall(self, call); + } + else + { + invokeId = call->invokeId; + } + + goto exit_function; + } + else + { + struct sWriteGoCBVariablesParameter* param = (struct sWriteGoCBVariablesParameter*) GLOBAL_MALLOC(sizeof(struct sWriteGoCBVariablesParameter)); + + call->specificParameter2.pointer = param; + + param->itemIds = itemIds; + param->values = values; + + param->currentItemId = LinkedList_getNext(itemIds); + param->currentValue = LinkedList_getNext(values); + param->domainId = StringUtils_copyString(domainId); + + char* variableId = (char*)LinkedList_getData(param->currentItemId); + MmsValue* value = (MmsValue*)LinkedList_getData(param->currentValue); + + MmsConnection_writeVariableAsync(self->connection, &(call->invokeId), &err, domainId, variableId, value, writeVariableHandler, self); + + param->originalInvokeId = call->invokeId; + + invokeId = call->invokeId; + + *error = iedConnection_mapMmsErrorToIedError(err); + + if (err != MMS_ERROR_NONE) + { + iedConnection_releaseOutstandingCall(self, call); + GLOBAL_FREEMEM(param->domainId); + GLOBAL_FREEMEM(param); + goto exit_function; + } + else + return invokeId; + } + +exit_function: + LinkedList_destroy(itemIds); + LinkedList_destroyStatic(values); + + return invokeId; +} diff --git a/src/iec61850/inc/iec61850_client.h b/src/iec61850/inc/iec61850_client.h index 5946bd00..9555d060 100644 --- a/src/iec61850/inc/iec61850_client.h +++ b/src/iec61850/inc/iec61850_client.h @@ -795,8 +795,9 @@ ClientGooseControlBlock_setDstAddress_appid(ClientGooseControlBlock self, uint16 ********************************************************/ /** - * \brief Read access to attributes of a GOOSE control block (GoCB) at the connected server. A GoCB contains - * the configuration values for a single GOOSE publisher. + * \brief Read access to attributes of a GOOSE control block (GoCB) at the connected server. + * + * A GoCB contains the configuration values for a single GOOSE publisher. * * The requested GoCB has to be specified by its object IEC 61850 ACSI object reference. E.g. * @@ -824,6 +825,42 @@ ClientGooseControlBlock_setDstAddress_appid(ClientGooseControlBlock self, uint16 LIB61850_API ClientGooseControlBlock IedConnection_getGoCBValues(IedConnection self, IedClientError* error, const char* goCBReference, ClientGooseControlBlock updateGoCB); +typedef void +(*IedConnection_GetGoCBValuesHandler) (uint32_t invokeId, void* parameter, IedClientError err, ClientGooseControlBlock goCB); + +/** + * \brief Read access to attributes of a GOOSE control block (GoCB) at the connected server (async version) + * + * A GoCB contains the configuration values for a single GOOSE publisher. + * + * The requested GoCB has to be specified by its object IEC 61850 ACSI object reference. E.g. + * + * "simpleIOGernericIO/LLN0.gcbEvents" + * + * This function is used to perform the actual read service for the GoCB values. + * To access the received values the functions of ClientGooseControlBlock have to be used. + * + * If called with a NULL argument for the updateGoCB parameter a new ClientGooseControlBlock instance is created + * and populated with the values received by the server. It is up to the user to release this object by + * calling the ClientGooseControlBlock_destroy function when the object is no longer needed. If called with a reference + * to an existing ClientGooseControlBlock instance the values of the attributes will be updated and no new instance + * will be created. + * + * Note: This function maps to a single MMS read request to retrieve the complete GoCB at once. + * + * \param connection the connection object + * \param error the error code if an error occurs + * \param goCBReference IEC 61850-7-2 ACSI object reference of the GOOSE control block + * \param updateRcb a reference to an existing ClientGooseControlBlock instance or NULL + * \param handler the user callback that is called when the service is completed or timed out + * \param parameter user provided parameter that is passed to the callback handler + * + * \return the invoke ID of the request + */ +LIB61850_API uint32_t +IedConnection_getGoCBValuesAsync(IedConnection self, IedClientError* error, const char* goCBReference, ClientGooseControlBlock updateGoCB, + IedConnection_GetGoCBValuesHandler handler, void* parameter); + /** * \brief Write access to attributes of a GOOSE control block (GoCB) at the connected server * @@ -849,6 +886,35 @@ LIB61850_API void IedConnection_setGoCBValues(IedConnection self, IedClientError* error, ClientGooseControlBlock goCB, uint32_t parametersMask, bool singleRequest); +/** + * \brief Write access to attributes of a GOOSE control block (GoCB) at the connected server (async version) + * + * The GoCB and the values to be written are specified with the goCB parameter. + * + * The parametersMask parameter specifies which attributes of the remote GoCB have to be set by this request. + * You can specify multiple attributes by ORing the defined bit values. If all attributes have to be written + * GOCB_ELEMENT_ALL can be used. + * + * The singleRequest parameter specifies the mapping to the corresponding MMS write request. Standard compliant + * servers should accept both variants. But some server accept only one variant. Then the value of this parameter + * will be of relevance. + * + * \param connection the connection object + * \param error the error code if an error occurs + * \param goCB ClientGooseControlBlock instance that actually holds the parameter + * values to be written. + * \param parametersMask specifies the parameters contained in the setGoCBValues request. + * \param singleRequest specifies if the seGoCBValues services is mapped to a single MMS write request containing + * multiple variables or to multiple MMS write requests. + * \param handler the user callback that is called when the service is completed or timed out + * \param parameter user provided parameter that is passed to the callback handler + * + * \return the invoke ID of the request + */ +LIB61850_API uint32_t +IedConnection_setGoCBValuesAsync(IedConnection self, IedClientError* error, ClientGooseControlBlock goCB, + uint32_t parametersMask, bool singleRequest, IedConnection_GenericServiceHandler handler, void* parameter); + /** @} */ diff --git a/src/mms/inc/iso_connection_parameters.h b/src/mms/inc/iso_connection_parameters.h index df413579..cc443daf 100644 --- a/src/mms/inc/iso_connection_parameters.h +++ b/src/mms/inc/iso_connection_parameters.h @@ -235,7 +235,6 @@ IsoConnectionParameters_setTcpParameters(IsoConnectionParameters self, const cha LIB61850_API void IsoConnectionParameters_setLocalTcpParameters(IsoConnectionParameters self, const char* localIpAddress, int localTcpPort); - /** * \brief set the remote AP-Title and AE-Qualifier * diff --git a/src/mms/inc/mms_client_connection.h b/src/mms/inc/mms_client_connection.h index 7447b33b..73e61cb1 100644 --- a/src/mms/inc/mms_client_connection.h +++ b/src/mms/inc/mms_client_connection.h @@ -281,6 +281,8 @@ MmsConnection_destroy(MmsConnection self); LIB61850_API bool MmsConnection_connect(MmsConnection self, MmsError* mmsError, const char* serverName, int serverPort); + + LIB61850_API void MmsConnection_connectAsync(MmsConnection self, MmsError* mmsError, const char* serverName, int serverPort);