diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index fafc146b..438f2474 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -22,6 +22,7 @@ add_subdirectory(iec61850_client_example_reporting) add_subdirectory(iec61850_client_example_log) add_subdirectory(iec61850_client_example_array) add_subdirectory(iec61850_client_example_files) +add_subdirectory(iec61850_client_example_async) if(WIN32) if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/../third_party/winpcap/Lib/wpcap.lib") diff --git a/examples/iec61850_client_example_async/CMakeLists.txt b/examples/iec61850_client_example_async/CMakeLists.txt new file mode 100644 index 00000000..87ec9b79 --- /dev/null +++ b/examples/iec61850_client_example_async/CMakeLists.txt @@ -0,0 +1,17 @@ + +set(iec61850_client_async_SRCS + client_example_async.c +) + +IF(WIN32) +set_source_files_properties(${iec61850_client_async_SRCS} + PROPERTIES LANGUAGE CXX) +ENDIF(WIN32) + +add_executable(iec61850_client_async + ${iec61850_client_async_SRCS} +) + +target_link_libraries(iec61850_client_async + iec61850 +) diff --git a/examples/iec61850_client_example_async/client_example_async.c b/examples/iec61850_client_example_async/client_example_async.c new file mode 100644 index 00000000..ed8627de --- /dev/null +++ b/examples/iec61850_client_example_async/client_example_async.c @@ -0,0 +1,145 @@ +/* + * client_example_async.c + * + * Shows how to use the asynchronous client API + * + * This example is intended to be used with server_example_basic_io or server_example_goose. + */ + +#include "iec61850_client.h" + +#include +#include + +#include "hal_thread.h" + +void +reportCallbackFunction(void* parameter, ClientReport report) +{ + MmsValue* dataSetValues = ClientReport_getDataSetValues(report); + + printf("received report for %s\n", ClientReport_getRcbReference(report)); + + int i; + for (i = 0; i < 4; i++) { + ReasonForInclusion reason = ClientReport_getReasonForInclusion(report, i); + + if (reason != IEC61850_REASON_NOT_INCLUDED) { + printf(" GGIO1.SPCSO%i.stVal: %i (included for reason %i)\n", i, + MmsValue_getBoolean(MmsValue_getElement(dataSetValues, i)), reason); + } + } +} + +static void +printValue(char* name, MmsValue* value) +{ + char buf[1000]; + + MmsValue_printToBuffer(value, buf, 1000); + + printf("Received value for %s: %s\n", name, buf); +} + +static void +readObjectHandler (int invokeId, void* parameter, IedClientError err, MmsValue* value) +{ + if (err == IED_ERROR_OK) { + printValue((char*) parameter, value); + + MmsValue_delete(value); + } + else { + printf("Failed to read object %s (err=%i)\n", parameter, err); + } +} + +static void +getVarSpecHandler (int invokeId, void* parameter, IedClientError err, MmsVariableSpecification* spec) +{ + if (err == IED_ERROR_OK) { + printf("variable: %s has type %d\n", (char*) parameter, MmsVariableSpecification_getType(spec)); + + MmsVariableSpecification_destroy(spec); + } + else { + printf("Failed to get variable specification for object %s (err=%i)\n", parameter, err); + } +} + +int main(int argc, char** argv) { + + char* hostname; + int tcpPort = 102; + + if (argc > 1) + hostname = argv[1]; + else + hostname = "localhost"; + + if (argc > 2) + tcpPort = atoi(argv[2]); + + IedClientError error; + + IedConnection con = IedConnection_create(); + + IedConnection_connectAsync(con, &error, hostname, tcpPort); + + if (error == IED_ERROR_OK) { + + bool success = true; + + while (IedConnection_getState(con) != IED_STATE_CONNECTED) { + + if (IedConnection_getState(con) == IED_STATE_CLOSED) { + success = false; + break; + } + + Thread_sleep(10); + } + + if (success) { + + IedConnection_readObjectAsync(con, &error, "simpleIOGenericIO/GGIO1.AnIn1.mag.f", IEC61850_FC_MX, readObjectHandler, "simpleIOGenericIO/GGIO1.AnIn1.mag.f"); + + if (error != IED_ERROR_OK) { + printf("read object error %i\n", error); + } + + IedConnection_readObjectAsync(con, &error, "simpleIOGenericIO/GGIO1.AnIn2.mag.f", IEC61850_FC_MX, readObjectHandler, "simpleIOGenericIO/GGIO1.AnIn2.mag.f"); + + if (error != IED_ERROR_OK) { + printf("read object error %i\n", error); + } + + IedConnection_getVariableSpecificationAsync(con, &error, "simpleIOGenericIO/GGIO1.AnIn1", IEC61850_FC_MX, getVarSpecHandler, "simpleIOGenericIO/GGIO1.AnIn1"); + + if (error != IED_ERROR_OK) { + printf("get variable specification error %i\n", error); + } + } + + Thread_sleep(1000); + + IedConnection_releaseAsync(con, &error); + + if (error != IED_ERROR_OK) { + printf("Release returned error: %d\n", error); + } + else { + + while (IedConnection_getState(con) != IED_STATE_CLOSED) { + Thread_sleep(10); + } + } + } + else { + printf("Failed to connect to %s:%i\n", hostname, tcpPort); + } + + IedConnection_destroy(con); +} + + diff --git a/src/iec61850/client/ied_connection.c b/src/iec61850/client/ied_connection.c index ed9e1ba9..82ed6341 100644 --- a/src/iec61850/client/ied_connection.c +++ b/src/iec61850/client/ied_connection.c @@ -494,6 +494,8 @@ createNewConnectionObject(TLSConfiguration tlsConfig) self->outstandingCalls = (IedConnectionOutstandingCall) GLOBAL_CALLOC(OUTSTANDING_CALLS, sizeof(struct sIedConnectionOutstandingCall)); self->connectionTimeout = DEFAULT_CONNECTION_TIMEOUT; + + MmsConnection_setInformationReportHandler(self->connection, informationReportHandler, self); } return self; @@ -545,6 +547,7 @@ IedConnection_installConnectionClosedHandler(IedConnection self, IedConnectionCl self->connectionClosedParameter = parameter; } +//TODO remove - not required - replace by mmsConnectionStateChangedHandler static void connectionLostHandler(MmsConnection connection, void* parameter) { @@ -562,12 +565,12 @@ connectionLostHandler(MmsConnection connection, void* parameter) void IedConnection_connect(IedConnection self, IedClientError* error, const char* hostname, int tcpPort) { - MmsError mmsError; - if (IedConnection_getState(self) != IED_STATE_CONNECTED) { + MmsError mmsError; + MmsConnection_setConnectionLostHandler(self->connection, NULL, NULL); - MmsConnection_setInformationReportHandler(self->connection, informationReportHandler, self); + MmsConnection_setConnectTimeout(self->connection, self->connectionTimeout); @@ -585,6 +588,44 @@ IedConnection_connect(IedConnection self, IedClientError* error, const char* hos *error = IED_ERROR_ALREADY_CONNECTED; } +static void +mmsConnectionStateChangedHandler(MmsConnection connection, void* parameter, MmsConnectionState newState) +{ + IedConnection self = (IedConnection) parameter; + + printf("state changed: %d\n", newState); + + if (newState == MMS_CONNECTION_STATE_CONNECTED) { + IedConnection_setState(self, IED_STATE_CONNECTED); + MmsConnection_setConnectionLostHandler(self->connection, connectionLostHandler, (void*) self); + } + else if (newState == MMS_CONNECTION_STATE_CLOSED) { + IedConnection_setState(self, IED_STATE_CLOSED); + } +} + +void +IedConnection_connectAsync(IedConnection self, IedClientError* error, const char* hostname, int tcpPort) +{ + if (IedConnection_getState(self) != IED_STATE_CONNECTED) { + + MmsError mmsError = MMS_ERROR_NONE; + + MmsConnection_setConnectTimeout(self->connection, self->connectionTimeout); + MmsConnection_setConnectionLostHandler(self->connection, NULL, NULL); + + //TODO move to createNewConnectionObject function + MmsConnection_setConnectionStateChangedHandler(self->connection, mmsConnectionStateChangedHandler, self); + + + MmsConnection_connectAsync(self->connection, &mmsError, hostname, tcpPort); + + *error = iedConnection_mapMmsErrorToIedError(mmsError); + } + else + *error = IED_ERROR_ALREADY_CONNECTED; +} + void IedConnection_abort(IedConnection self, IedClientError* error) { @@ -601,6 +642,21 @@ IedConnection_abort(IedConnection self, IedClientError* error) *error = IED_ERROR_NOT_CONNECTED; } +void +IedConnection_abortAsync(IedConnection self, IedClientError* error) +{ + if (IedConnection_getState(self) == IED_STATE_CONNECTED) { + + MmsError mmsError; + + MmsConnection_abortAsync(self->connection, &mmsError); + + *error = iedConnection_mapMmsErrorToIedError(mmsError); + } + else + *error = IED_ERROR_NOT_CONNECTED; +} + void IedConnection_release(IedConnection self, IedClientError* error) { @@ -615,6 +671,20 @@ IedConnection_release(IedConnection self, IedClientError* error) *error = IED_ERROR_NOT_CONNECTED; } +void +IedConnection_releaseAsync(IedConnection self, IedClientError* error) +{ + if (IedConnection_getState(self) == IED_STATE_CONNECTED) { + MmsError mmsError; + + MmsConnection_concludeAsync(self->connection, &mmsError, NULL, NULL); + + *error = iedConnection_mapMmsErrorToIedError(mmsError); + } + else + *error = IED_ERROR_NOT_CONNECTED; +} + void IedConnection_close(IedConnection self) { @@ -648,41 +718,6 @@ IedConnection_destroy(IedConnection self) GLOBAL_FREEMEM(self); } - -MmsVariableSpecification* -IedConnection_getVariableSpecification(IedConnection self, IedClientError* error, const char* objectReference, - FunctionalConstraint fc) -{ - char domainIdBuffer[65]; - char itemIdBuffer[129]; - - char* domainId; - char* itemId; - - MmsError mmsError; - MmsVariableSpecification* varSpec = NULL; - - domainId = MmsMapping_getMmsDomainFromObjectReference(objectReference, domainIdBuffer); - itemId = MmsMapping_createMmsVariableNameFromObjectReference(objectReference, fc, itemIdBuffer); - - if ((domainId == NULL) || (itemId == NULL)) { - *error = IED_ERROR_OBJECT_REFERENCE_INVALID; - goto cleanup_and_exit; - } - - varSpec = - MmsConnection_getVariableAccessAttributes(self->connection, &mmsError, domainId, itemId); - - if (varSpec != NULL) - *error = IED_ERROR_OK; - else - *error = iedConnection_mapMmsErrorToIedError(mmsError); - - cleanup_and_exit: - - return varSpec; -} - static IedConnectionOutstandingCall allocateOutstandingCall(IedConnection self) { @@ -739,11 +774,108 @@ lookupOutstandingCall(IedConnection self, uint32_t invokeId) return call; } +MmsVariableSpecification* +IedConnection_getVariableSpecification(IedConnection self, IedClientError* error, const char* objectReference, + FunctionalConstraint fc) +{ + char domainIdBuffer[65]; + char itemIdBuffer[129]; + + char* domainId; + char* itemId; + + MmsError mmsError; + MmsVariableSpecification* varSpec = NULL; + + domainId = MmsMapping_getMmsDomainFromObjectReference(objectReference, domainIdBuffer); + itemId = MmsMapping_createMmsVariableNameFromObjectReference(objectReference, fc, itemIdBuffer); + + if ((domainId == NULL) || (itemId == NULL)) { + *error = IED_ERROR_OBJECT_REFERENCE_INVALID; + goto cleanup_and_exit; + } + + varSpec = + MmsConnection_getVariableAccessAttributes(self->connection, &mmsError, domainId, itemId); + + if (varSpec != NULL) + *error = IED_ERROR_OK; + else + *error = iedConnection_mapMmsErrorToIedError(mmsError); + +cleanup_and_exit: + + return varSpec; +} + static void -readObjectHandlerInternal(int invokeId, void* parameter, MmsError err, MmsValue* value) +getAccessAttrHandler(int invokeId, void* parameter, MmsError err, MmsVariableSpecification* typeSpec) { - printf("readObjectHandlerInternal %i\n", invokeId); + IedConnection self = (IedConnection) parameter; + + IedConnectionOutstandingCall call = lookupOutstandingCall(self, invokeId); + if (call) { + + IedConnection_GetVariableSpecificationHandler handler = (IedConnection_GetVariableSpecificationHandler) call->callback; + + handler(invokeId, call->callbackParameter, iedConnection_mapMmsErrorToIedError(err), typeSpec); + + releaseOutstandingCall(self, call); + } + else { + if (DEBUG_IED_CLIENT) + printf("IED_CLIENT: internal error - no matching outstanding call!\n"); + } +} + +uint32_t +IedConnection_getVariableSpecificationAsync(IedConnection self, IedClientError* error, const char* dataAttributeReference, + FunctionalConstraint fc, IedConnection_GetVariableSpecificationHandler handler, void* parameter) +{ + uint32_t invokeId = 0; + + char domainIdBuffer[65]; + char itemIdBuffer[129]; + + char* domainId; + char* itemId; + + MmsError mmsError; + MmsVariableSpecification* varSpec = NULL; + + domainId = MmsMapping_getMmsDomainFromObjectReference(dataAttributeReference, domainIdBuffer); + itemId = MmsMapping_createMmsVariableNameFromObjectReference(dataAttributeReference, fc, itemIdBuffer); + + if ((domainId == NULL) || (itemId == NULL)) { + *error = IED_ERROR_OBJECT_REFERENCE_INVALID; + goto cleanup_and_exit; + } + + IedConnectionOutstandingCall call = allocateOutstandingCall(self); + + if (call == NULL) { + *error = IED_ERROR_OUTSTANDING_CALL_LIMIT_REACHED; + return 0; + } + + call->callback = handler; + call->callbackParameter = parameter; + + call->invokeId = MmsConnection_getVariableAccessAttributesAsync(self->connection, &mmsError, domainId, itemId, getAccessAttrHandler, self); + + invokeId = call->invokeId; + + *error = iedConnection_mapMmsErrorToIedError(mmsError); + +cleanup_and_exit: + + return invokeId; +} + +static void +readObjectHandlerInternal(int invokeId, void* parameter, MmsError err, MmsValue* value) +{ IedConnection self = (IedConnection) parameter; IedConnectionOutstandingCall call = lookupOutstandingCall(self, invokeId); @@ -757,7 +889,7 @@ readObjectHandlerInternal(int invokeId, void* parameter, MmsError err, MmsValue* releaseOutstandingCall(self, call); } else { - // if (DEBUG_IED_CLIENT) + if (DEBUG_IED_CLIENT) printf("IED_CLIENT: internal error - no matching outstanding call!\n"); } } @@ -1144,6 +1276,102 @@ IedConnection_writeObject(IedConnection self, IedClientError* error, const char* *error = iedConnection_mapMmsErrorToIedError(mmsError); } +static void +writeVariableHandler(int invokeId, void* parameter, MmsError err, MmsDataAccessError accessError) +{ + IedConnection self = (IedConnection) parameter; + + IedConnectionOutstandingCall call = lookupOutstandingCall(self, invokeId); + + if (call) { + + IedConnection_WriteObjectHandler handler = (IedConnection_WriteObjectHandler) call->callback; + + IedClientError iedError = iedConnection_mapMmsErrorToIedError(err); + + if (iedError == IED_ERROR_OK) + iedError = iedConnection_mapDataAccessErrorToIedError(accessError); + + handler(invokeId, call->callbackParameter, iedError); + + releaseOutstandingCall(self, call); + } + else { + if (DEBUG_IED_CLIENT) + printf("IED_CLIENT: internal error - no matching outstanding call!\n"); + } +} + +typedef void +(*IedConnection_WriteObjectHandler) (int invokeId, void* parameter, IedClientError err); + +uint32_t +IedConnection_writeObjectAsync(IedConnection self, IedClientError* error, const char* objectReference, + FunctionalConstraint fc, MmsValue* value, IedConnection_WriteObjectHandler handler, void* parameter) +{ + char domainIdBuffer[65]; + char itemIdBuffer[65]; + + char* domainId; + char* itemId; + + domainId = MmsMapping_getMmsDomainFromObjectReference(objectReference, domainIdBuffer); + itemId = MmsMapping_createMmsVariableNameFromObjectReference(objectReference, fc, itemIdBuffer); + + if ((domainId == NULL) || (itemId == NULL)) { + *error = IED_ERROR_OBJECT_REFERENCE_INVALID; + return 0; + } + + IedConnectionOutstandingCall call = allocateOutstandingCall(self); + + if (call == NULL) { + *error = IED_ERROR_OUTSTANDING_CALL_LIMIT_REACHED; + return 0; + } + + call->callback = handler; + call->callbackParameter = parameter; + call->invokeId = 0; + + MmsError err = MMS_ERROR_NONE; + + /* check if item ID contains an array "(..)" */ + char* brace = strchr(itemId, '('); + + if (brace) { + char* secondBrace = strchr(brace, ')'); + + if (secondBrace) { + char* endPtr; + + int index = (int) strtol(brace + 1, &endPtr, 10); + + if (endPtr == secondBrace) { + char* component = NULL; + + if (strlen(secondBrace + 1) > 1) + component = secondBrace + 2; /* skip "." after array element specifier */ + + *brace = 0; + + call->invokeId = MmsConnection_writeSingleArrayElementWithComponentAsync(self->connection, &err, domainId, itemId, index, component, value, + writeVariableHandler, self); + } + else + *error = IED_ERROR_USER_PROVIDED_INVALID_ARGUMENT; + } + else + *error = IED_ERROR_USER_PROVIDED_INVALID_ARGUMENT; + } + else + call->invokeId = MmsConnection_writeVariableAsync(self->connection, &err, domainId, itemId, value, writeVariableHandler, self); + + *error = iedConnection_mapMmsErrorToIedError(err); + + return call->invokeId; +} + void IedConnection_writeBooleanValue(IedConnection self, IedClientError* error, const char* objectReference, FunctionalConstraint fc, bool value) diff --git a/src/iec61850/inc/iec61850_client.h b/src/iec61850/inc/iec61850_client.h index f2222092..5b82f67d 100644 --- a/src/iec61850/inc/iec61850_client.h +++ b/src/iec61850/inc/iec61850_client.h @@ -235,6 +235,8 @@ IedConnection_setConnectTimeout(IedConnection self, uint32_t timeoutInMs); /** * \brief Connect to a server * + * NOTE: Function will block until connection is up or timeout happened. + * * \param self the connection object * \param error the error code if an error occurs * \param hostname the host name or IP address of the server to connect to @@ -243,6 +245,17 @@ IedConnection_setConnectTimeout(IedConnection self, uint32_t timeoutInMs); void IedConnection_connect(IedConnection self, IedClientError* error, const char* hostname, int tcpPort); +/** + * \brief Asynchronously connect to a server + * + * \param self the connection object + * \param error the error code if an error occurs + * \param hostname the host name or IP address of the server to connect to + * \param tcpPort the TCP port number of the server to connect to + */ +void +IedConnection_connectAsync(IedConnection self, IedClientError* error, const char* hostname, int tcpPort); + /** * \brief Abort the connection * @@ -258,6 +271,22 @@ IedConnection_connect(IedConnection self, IedClientError* error, const char* hos void IedConnection_abort(IedConnection self, IedClientError* error); +/** + * \brief Asynchronously abort the connection + * + * This will close the MMS association by sending an ACSE abort message to the server. + * After sending the abort message the connection is closed immediately. + * If the connection is not in "connected" state an IED_ERROR_NOT_CONNECTED error will be reported. + * + * NOTE: This function works asynchronously. The IedConnection object should not be destroyed before the + * connection state changes to IED_STATE_CLOSED. + * + * \param self the connection object + * \param error the error code if an error occurs + */ +void +IedConnection_abortAsync(IedConnection self, IedClientError* error); + /** * \brief Release the connection * @@ -273,6 +302,21 @@ IedConnection_abort(IedConnection self, IedClientError* error); void IedConnection_release(IedConnection self, IedClientError* error); +/** + * \brief Asynchronously release the connection + * + * This will release the MMS association by sending an MMS conclude message to the server. + * The client can NOT assume the connection to be closed when the function returns, It can + * also fail if the server returns with a negative response. To be sure that the connection + * will be close the close or abort methods should be used. If the connection is not in "connected" state an + * IED_ERROR_NOT_CONNECTED error will be reported. + * + * \param self the connection object + * \param error the error code if an error occurs + */ +void +IedConnection_releaseAsync(IedConnection self, IedClientError* error); + /** * \brief Close the connection * @@ -1239,6 +1283,13 @@ void IedConnection_writeObject(IedConnection self, IedClientError* error, const char* dataAttributeReference, FunctionalConstraint fc, MmsValue* value); +typedef void +(*IedConnection_WriteObjectHandler) (int invokeId, void* parameter, IedClientError err); + +uint32_t +IedConnection_writeObjectAsync(IedConnection self, IedClientError* error, const char* objectReference, + FunctionalConstraint fc, MmsValue* value, IedConnection_WriteObjectHandler handler, void* parameter); + /** * \brief read a functional constrained data attribute (FCDA) of type boolean @@ -1956,6 +2007,13 @@ MmsVariableSpecification* IedConnection_getVariableSpecification(IedConnection self, IedClientError* error, const char* dataAttributeReference, FunctionalConstraint fc); +typedef void +(*IedConnection_GetVariableSpecificationHandler) (int invokeId, void* parameter, IedClientError err, MmsVariableSpecification* spec); + +uint32_t +IedConnection_getVariableSpecificationAsync(IedConnection self, IedClientError* error, const char* dataAttributeReference, + FunctionalConstraint fc, IedConnection_GetVariableSpecificationHandler handler, void* parameter); + /** @} */ /** diff --git a/src/mms/iso_mms/client/mms_client_connection.c b/src/mms/iso_mms/client/mms_client_connection.c index 3836370f..942ca4cc 100644 --- a/src/mms/iso_mms/client/mms_client_connection.c +++ b/src/mms/iso_mms/client/mms_client_connection.c @@ -1779,6 +1779,8 @@ MmsConnection_concludeAsync(MmsConnection self, MmsError* mmsError, MmsConnectio goto exit_function; } + *mmsError = MMS_ERROR_NONE; + ByteBuffer* concludeMessage = IsoClientConnection_allocateTransmitBuffer(self->isoClient); mmsClient_createConcludeRequest(self, concludeMessage);