diff --git a/examples/iec61850_client_example_async/client_example_async.c b/examples/iec61850_client_example_async/client_example_async.c index 60d835a7..08568284 100644 --- a/examples/iec61850_client_example_async/client_example_async.c +++ b/examples/iec61850_client_example_async/client_example_async.c @@ -3,7 +3,7 @@ * * Shows how to use the asynchronous client API * - * This example is intended to be used with server_example_basic_io or server_example_goose. + * This example is intended to be used with server_example_basic_io. */ #include "iec61850_client.h" @@ -15,6 +15,29 @@ 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); +} + +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) { @@ -22,13 +45,54 @@ readDataSetHandler(uint32_t invokeId, void* parameter, IedClientError err, Clien 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 +static void +writeDataSetHandler(uint32_t invokeId, void* parameter, IedClientError err, LinkedList /* */accessResults) +{ + if (err == IED_ERROR_OK) { + + if (accessResults) { + + int i = 0; + + LinkedList element = LinkedList_getNext(accessResults); + + while (element) { + MmsValue* accessResultValue = LinkedList_getData(element); + + printf(" access-result[%i]", i); + printValue("", accessResultValue); + + + element = LinkedList_getNext(element); + i++; + } + + LinkedList_destroyDeep(accessResults, (LinkedListValueDeleteFunction) MmsValue_delete); + + } + } + else { + printf("Failed to write data set (err=%i)\n", err); + } +} + +static void reportCallbackFunction(void* parameter, ClientReport report) { MmsValue* dataSetValues = ClientReport_getDataSetValues(report); @@ -46,29 +110,6 @@ reportCallbackFunction(void* parameter, ClientReport report) } } -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 (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 getVarSpecHandler (uint32_t invokeId, void* parameter, IedClientError err, MmsVariableSpecification* spec) { @@ -204,6 +245,20 @@ int main(int argc, char** argv) { if (error != IED_ERROR_OK) { printf("read data set error %i\n", error); } + + 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); + } + + LinkedList_destroyDeep(values, (LinkedListValueDeleteFunction) MmsValue_delete); } Thread_sleep(1000); diff --git a/src/iec61850/client/ied_connection.c b/src/iec61850/client/ied_connection.c index 9e3232ed..96aab4fc 100644 --- a/src/iec61850/client/ied_connection.c +++ b/src/iec61850/client/ied_connection.c @@ -3121,6 +3121,102 @@ exit_function: return; } +static void +writeDataSetHandlerInternal(uint32_t invokeId, void* parameter, MmsError err, LinkedList /* */ accessResults) +{ + IedConnection self = (IedConnection) parameter; + + IedConnectionOutstandingCall call = iedConnection_lookupOutstandingCall(self, invokeId); + + if (call) { + + IedConnection_WriteDataSetHandler handler = (IedConnection_WriteDataSetHandler) call->callback; + + handler(invokeId, call->callbackParameter, iedConnection_mapMmsErrorToIedError(err), accessResults); + + iedConnection_releaseOutstandingCall(self, call); + } + else { + if (DEBUG_IED_CLIENT) + printf("IED_CLIENT: internal error - no matching outstanding call!\n"); + } +} + +LIB61850_API uint32_t +IedConnection_writeDataSetValuesAsync(IedConnection self, IedClientError* error, const char* dataSetReference, + LinkedList/**/ values, IedConnection_WriteDataSetHandler handler, void* parameter) +{ + char domainIdBuffer[65]; + char itemIdBuffer[DATA_SET_MAX_NAME_LENGTH + 1]; + + const char* domainId = NULL; + const char* itemId = NULL; + + 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; + + MmsError err = MMS_ERROR_NONE; + + call->invokeId = MmsConnection_writeNamedVariableListAsync(self->connection, &err, isAssociationSpecific, domainId, itemId, values, writeDataSetHandlerInternal, self); + + if ((err != MMS_ERROR_NONE) || (*error != IED_ERROR_OK)) { + + if (err != MMS_ERROR_NONE) + *error = iedConnection_mapMmsErrorToIedError(err); + + iedConnection_releaseOutstandingCall(self, call); + + return 0; + } + + return call->invokeId; +} + LinkedList /* */ IedConnection_queryLogByTime(IedConnection self, IedClientError* error, const char* logReference, uint64_t startTime, uint64_t endTime, bool* moreFollows) diff --git a/src/iec61850/inc/iec61850_client.h b/src/iec61850/inc/iec61850_client.h index 5fc728c2..87fdb868 100644 --- a/src/iec61850/inc/iec61850_client.h +++ b/src/iec61850/inc/iec61850_client.h @@ -1585,11 +1585,11 @@ typedef void * \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 -*/ + * \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); @@ -1638,9 +1638,9 @@ IedConnection_deleteDataSet(IedConnection self, IedClientError* error, const cha * this object references is LDName/LNodeName.item(arrayIndex)component[FC]. * * \param connection the connection object - * \param error the error code if an error occurs + * \param[out] error the error code if an error occurs * \param dataSetReference object reference of the data set - * \param isDeletable this is an output parameter indicating that the requested data set is deletable by clients. + * \param[out] isDeletable this is an output parameter indicating that the requested data set is deletable by clients. * If this information is not required a NULL pointer can be used. * * \return LinkedList containing the data set elements as char* strings. @@ -1658,15 +1658,51 @@ IedConnection_getDataSetDirectory(IedConnection self, IedClientError* error, con * contains a value for each data set member. * * \param connection the connection object - * \param error the error code if an error occurs + * \param[out] error the error code if an error occurs * \param dataSetReference object reference of the data set * \param values the new data set values - * \param accessResults the access results for each data set member + * \param[out] accessResults the access results for each data set member */ LIB61850_API void IedConnection_writeDataSetValues(IedConnection self, IedClientError* error, const char* dataSetReference, LinkedList/**/ values, /* OUTPUT */LinkedList* /* */accessResults); +/** + * \brief Callback handler for asynchronous write data set values services (set data set) + * + * \param invokeId the invoke ID of the service request + * \param parameter used provided parameter + * \param err the error code if an error occurs + * \param accessResults the list of access results for the data set entries. + */ +typedef void +(*IedConnection_WriteDataSetHandler) (uint32_t invokeId, void* parameter, IedClientError err, LinkedList /* */accessResults); + +/** + * \brief Write the data set values to the server - async version + * + * The parameter dataSetReference is the name of the data set to write. 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 values parameter has to be the same number of elements as are members in the data set. + * + * When the service call had been successful the \ref IedConnection_WriteDataSetHandler is called with an error value of + * IED_ERROR_OK and a list of MmsValue instances of type data access error. These describe the access results of the individual + * data set entries. + * + * \param connection the connection object + * \param[out] error the error code if an error occurs + * \param dataSetReference object reference of the data set + * \param values the new data set values + * \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_writeDataSetValuesAsync(IedConnection self, IedClientError* error, const char* dataSetReference, + LinkedList/**/ values, IedConnection_WriteDataSetHandler handler, void* parameter); + /******************************************************** * Data set object (local representation of a data set)