From c04b4e928dbcbfaf3c0c024c0abb0d834d6ee586 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Sat, 3 Nov 2018 11:46:32 +0100 Subject: [PATCH] - IEC 61850 client: implemented IedConnection_readDataSetValuesAsync --- .../client_example_async.c | 24 ++++ src/iec61850/client/ied_connection.c | 132 +++++++++++++++++- src/iec61850/inc/iec61850_client.h | 71 +++++++++- 3 files changed, 220 insertions(+), 7 deletions(-) diff --git a/examples/iec61850_client_example_async/client_example_async.c b/examples/iec61850_client_example_async/client_example_async.c index 044444e3..60d835a7 100644 --- a/examples/iec61850_client_example_async/client_example_async.c +++ b/examples/iec61850_client_example_async/client_example_async.c @@ -13,6 +13,21 @@ #include "hal_thread.h" +static ClientDataSet clientDataSet = NULL; + +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)); + } + else { + printf("Failed to read data set (err=%i)\n", err); + } +} + void reportCallbackFunction(void* parameter, ClientReport report) { @@ -183,6 +198,12 @@ int main(int argc, char** argv) { if (error != IED_ERROR_OK) { printf("get variable specification error %i\n", error); } + + IedConnection_readDataSetValuesAsync(con, &error, "simpleIOGenericIO/LLN0.Events", NULL, readDataSetHandler, NULL); + + if (error != IED_ERROR_OK) { + printf("read data set error %i\n", error); + } } Thread_sleep(1000); @@ -203,6 +224,9 @@ int main(int argc, char** argv) { printf("Failed to connect to %s:%i\n", hostname, tcpPort); } + if (clientDataSet) + ClientDataSet_destroy(clientDataSet); + IedConnection_destroy(con); } diff --git a/src/iec61850/client/ied_connection.c b/src/iec61850/client/ied_connection.c index 0716238c..9e3232ed 100644 --- a/src/iec61850/client/ied_connection.c +++ b/src/iec61850/client/ied_connection.c @@ -195,9 +195,6 @@ iedConnection_lookupOutstandingCall(IedConnection self, uint32_t invokeId) int i = 0; for (i = 0; i < OUTSTANDING_CALLS; i++) { - - printf("%d: used: %d invokeId: %d\n", i, self->outstandingCalls[i].used, self->outstandingCalls[i].invokeId); - if ((self->outstandingCalls[i].used) && (self->outstandingCalls[i].invokeId == invokeId)) { call = &(self->outstandingCalls[i]); break; @@ -2937,6 +2934,135 @@ exit_function: return dataSet; } +static void +getDataSetHandlerInternal(uint32_t invokeId, void* parameter, MmsError err, MmsValue* value) +{ + IedConnection self = (IedConnection) parameter; + + IedConnectionOutstandingCall call = iedConnection_lookupOutstandingCall(self, invokeId); + + if (call) { + + IedConnection_ReadDataSetHandler handler = (IedConnection_ReadDataSetHandler) call->callback; + + ClientDataSet dataSet = (ClientDataSet) call->specificParameter; + char* dataSetReference = (char*) call->specificParameter2; + + if (value != NULL) { + + if (dataSet == NULL) { + dataSet = ClientDataSet_create(dataSetReference); + ClientDataSet_setDataSetValues(dataSet, value); + GLOBAL_FREEMEM(dataSetReference); + } + else { + MmsValue* dataSetValues = ClientDataSet_getValues(dataSet); + MmsValue_update(dataSetValues, value); + MmsValue_delete(value); + } + } + + handler(invokeId, call->callbackParameter, iedConnection_mapMmsErrorToIedError(err), dataSet); + + iedConnection_releaseOutstandingCall(self, call); + } + else { + if (DEBUG_IED_CLIENT) + printf("IED_CLIENT: internal error - no matching outstanding call!\n"); + } +} + +uint32_t +IedConnection_readDataSetValuesAsync(IedConnection self, IedClientError* error, const char* dataSetReference, ClientDataSet dataSet, + IedConnection_ReadDataSetHandler handler, void* parameter) +{ + char domainIdBuffer[65]; + char itemIdBuffer[DATA_SET_MAX_NAME_LENGTH + 1]; + + const char* domainId = NULL; + const char* itemId = NULL; + + *error = IED_ERROR_OK; + + bool isAssociationSpecific = false; + + if (dataSetReference[0] != '@') { + + if ((dataSetReference[0] == '/') || (strchr(dataSetReference, '/') == NULL)) { + domainId = NULL; + + if (dataSetReference[0] == '/') + itemId = dataSetReference + 1; + else + itemId = dataSetReference; + } + else { + domainId = MmsMapping_getMmsDomainFromObjectReference(dataSetReference, domainIdBuffer); + + if (domainId == NULL) { + *error = IED_ERROR_OBJECT_REFERENCE_INVALID; + return 0; + } + + const char* itemIdRefOrig = dataSetReference + strlen(domainId) + 1; + + if (strlen(itemIdRefOrig) > DATA_SET_MAX_NAME_LENGTH) { + *error = IED_ERROR_OBJECT_REFERENCE_INVALID; + return 0; + } + + char* itemIdRef = StringUtils_copyStringToBuffer(itemIdRefOrig, itemIdBuffer); + + StringUtils_replace(itemIdRef, '.', '$'); + itemId = itemIdRef; + } + } + else { + itemId = dataSetReference + 1; + isAssociationSpecific = true; + } + + + 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 = dataSet; + + if (dataSet == NULL) + call->specificParameter2 = StringUtils_copyString(dataSetReference); + else + call->specificParameter2 = NULL; + + MmsError err = MMS_ERROR_NONE; + + if (isAssociationSpecific) + call->invokeId = MmsConnection_readNamedVariableListValuesAssociationSpecificAsync(self->connection, + &err, itemId, true, getDataSetHandlerInternal, self); + else + call->invokeId = MmsConnection_readNamedVariableListValuesAsync(self->connection, &err, + domainId, itemId, true, getDataSetHandlerInternal, self); + + if ((err != MMS_ERROR_NONE) || (*error != IED_ERROR_OK)) { + + if (err != MMS_ERROR_NONE) + *error = iedConnection_mapMmsErrorToIedError(err); + + GLOBAL_FREEMEM(call->specificParameter2); + + iedConnection_releaseOutstandingCall(self, call); + + return 0; + } + + return call->invokeId; +} + void IedConnection_writeDataSetValues(IedConnection self, IedClientError* error, const char* dataSetReference, LinkedList/**/ values, /* OUTPUT */LinkedList* /* */accessResults) diff --git a/src/iec61850/inc/iec61850_client.h b/src/iec61850/inc/iec61850_client.h index 549ef634..5fc728c2 100644 --- a/src/iec61850/inc/iec61850_client.h +++ b/src/iec61850/inc/iec61850_client.h @@ -411,8 +411,12 @@ typedef struct sClientSVControlBlock* ClientSVControlBlock; /** * \brief Create a new ClientSVControlBlock instance * - * This function simplifies client side access to server MSV/USV control blocks - * NOTE: Do not use the functions after the IedConnection object is invalidated! + * This function simplifies client side access to server MSV/USV control blocks + * NOTE: Do not use the functions after the IedConnection object is invalidated! + * + * The access functions cause synchronous read/write calls to the server. For asynchronous + * access use the \ref IedConnection_readObjectAsync and \ref IedConnection_writeObjectAsync + * functions. * * \param connection the IedConnection object with a valid connection to the server. * \param reference the object reference of the control block @@ -430,6 +434,13 @@ ClientSVControlBlock_create(IedConnection connection, const char* reference); LIB61850_API void ClientSVControlBlock_destroy(ClientSVControlBlock self); +/** + * \brief Test if this SVCB is multicast + * + * \param self the ClientSVControlBlock instance to operate on + * + * \return true if multicast SCVB, false otherwise (unicast) + */ LIB61850_API bool ClientSVControlBlock_isMulticast(ClientSVControlBlock self); @@ -508,6 +519,8 @@ ClientSVControlBlock_getSmpMod(ClientSVControlBlock self); * \brief returns number of ASDUs included in the SV message * * \param self the ClientSVControlBlock instance to operate on + * + * \return the number of ASDU included in a single SV message */ LIB61850_API int ClientSVControlBlock_getNoASDU(ClientSVControlBlock self); @@ -718,6 +731,18 @@ IedConnection_readObject(IedConnection self, IedClientError* error, const char* typedef void (*IedConnection_ReadObjectHandler) (uint32_t invokeId, void* parameter, IedClientError err, MmsValue* value); +/** + * \brief read a functional constrained data attribute (FCDA) or functional constrained data (FCD) - async version + * + * \param self the connection object to operate on + * \param error the error code if an error occurs + * \param object reference of the object/attribute to read + * \param fc the functional constraint of the data attribute or data object to read + * \param handler the user provided callback handler + * \param parameter user provided parameter that is passed to the callback handler + * + * \return the invoke ID of the request + */ LIB61850_API uint32_t IedConnection_readObjectAsync(IedConnection self, IedClientError* error, const char* objRef, FunctionalConstraint fc, IedConnection_ReadObjectHandler handler, void* parameter); @@ -738,11 +763,23 @@ IedConnection_writeObject(IedConnection self, IedClientError* error, const char* typedef void (*IedConnection_WriteObjectHandler) (uint32_t invokeId, void* parameter, IedClientError err); +/** + * \brief write a functional constrained data attribute (FCDA) or functional constrained data (FCD) - async version + * + * \param self the connection object to operate on + * \param error the error code if an error occurs + * \param object reference of the object/attribute to write + * \param fc the functional constraint of the data attribute or data object to write + * \param value the MmsValue to write (has to be of the correct type - MMS_STRUCTURE for FCD) + * \param handler the user provided callback handler + * \param parameter user provided parameter that is passed to the callback handler + * + * \return the invoke ID of the request + */ LIB61850_API 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 * @@ -1512,7 +1549,7 @@ ClientReportControlBlock_getOwner(ClientReportControlBlock self); */ /** - * \brief get data set values from a server device + * \brief get data set values from the server * * The parameter dataSetReference is the name of the data set to read. It is either in the form LDName/LNodeName.dataSetName * for permanent domain or VMD scope data sets or @dataSetName for an association specific data set. @@ -1531,6 +1568,32 @@ ClientReportControlBlock_getOwner(ClientReportControlBlock self); LIB61850_API ClientDataSet IedConnection_readDataSetValues(IedConnection self, IedClientError* error, const char* dataSetReference, ClientDataSet dataSet); +typedef void +(*IedConnection_ReadDataSetHandler) (uint32_t invokeId, void* parameter, IedClientError err, ClientDataSet dataSet); + +/** + * \brief get data set values from the server - async version + * + * The parameter dataSetReference is the name of the data set to read. It is either in the form LDName/LNodeName.dataSetName + * for permanent domain or VMD scope data sets or @dataSetName for an association specific data set. + * If the LDName part of the reference is missing the resulting data set will be of VMD scope. + * The received data set values are stored in a container object of type ClientDataSet that can be reused in a further + * service request. + * + * \param connection the connection object + * \param error the error code if an error occurs + * \param dataSetReference object reference of the data set + * \param dataSet a data set instance where to store the retrieved values or NULL if a new instance + * shall be created. +* \param handler the user provided callback handler +* \param parameter user provided parameter that is passed to the callback handler +* +* \return the invoke ID of the request +*/ +LIB61850_API uint32_t +IedConnection_readDataSetValuesAsync(IedConnection self, IedClientError* error, const char* dataSetReference, ClientDataSet dataSet, + IedConnection_ReadDataSetHandler handler, void* parameter); + /** * \brief create a new data set at the connected server device *