From 43e0fb4d05747b020e1d9028277599e4cd4780e2 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Mon, 5 Nov 2018 09:51:16 +0100 Subject: [PATCH] - IEC 61850 Client: ControlObjectClient - added async control actions --- .../client_example_async.c | 28 + src/iec61850/client/client_control.c | 606 ++++++++++++++++-- src/iec61850/client/ied_connection.c | 4 +- src/iec61850/inc/iec61850_client.h | 191 +++++- .../inc_private/ied_connection_private.h | 3 + 5 files changed, 754 insertions(+), 78 deletions(-) diff --git a/examples/iec61850_client_example_async/client_example_async.c b/examples/iec61850_client_example_async/client_example_async.c index 08568284..ecadf963 100644 --- a/examples/iec61850_client_example_async/client_example_async.c +++ b/examples/iec61850_client_example_async/client_example_async.c @@ -178,6 +178,12 @@ getServerDirectoryHandler(uint32_t invokeId, void* parameter, IedClientError err } } +static void +controlActionHandler(uint32_t invokeId, void* parameter, IedClientError err, ControlActionType type, bool success) +{ + printf("control: ID: %d type: %i err: %d success: %i\n", invokeId, type, err, success); +} + int main(int argc, char** argv) { char* hostname; @@ -259,6 +265,28 @@ int main(int argc, char** argv) { } LinkedList_destroyDeep(values, (LinkedListValueDeleteFunction) MmsValue_delete); + + Thread_sleep(1000); + + ControlObjectClient controlClient = ControlObjectClient_create("simpleIOGenericIO/GGIO1.SPCSO1", con); + + if (controlClient != NULL) { + + ControlObjectClient_setOrigin(controlClient, "test1", CONTROL_ORCAT_AUTOMATIC_REMOTE); + + MmsValue* ctlVal = MmsValue_newBoolean(true); + + ControlObjectClient_operateAsync(controlClient, &error, ctlVal, 0, controlActionHandler, NULL); + + if (error != IED_ERROR_OK) { + printf("Failed to send operate %i\n", error); + } + + } + else { + printf("Failed to connect to control object\n"); + } + } Thread_sleep(1000); diff --git a/src/iec61850/client/client_control.c b/src/iec61850/client/client_control.c index f54b8460..366ccf80 100644 --- a/src/iec61850/client/client_control.c +++ b/src/iec61850/client/client_control.c @@ -1,7 +1,7 @@ /* * client_control.c * - * Copyright 2013, 2014 Michael Zillgith + * Copyright 2013-2018 Michael Zillgith * * This file is part of libIEC61850. * @@ -110,41 +110,10 @@ resetLastApplError(ControlObjectClient self) } ControlObjectClient -ControlObjectClient_create(const char* objectReference, IedConnection connection) +ControlObjectClient_createEx(const char* objectReference, IedConnection connection, uint32_t ctlModel, MmsVariableSpecification* operSpec) { ControlObjectClient self = NULL; - /* request control model from server */ - char reference[129]; - - if (strlen(objectReference) < 121) { - strcpy(reference, objectReference); - strcat(reference, ".ctlModel"); - } - else - goto exit_function; - - IedClientError error; - - uint32_t ctlModel = IedConnection_readUnsigned32Value(connection, &error, reference, IEC61850_FC_CF); - - if (error != IED_ERROR_OK) { - if (DEBUG_IED_CLIENT) - printf("IED_CLIENT: ControlObjectClient_create: failed to get %s from server\n", reference); - - goto exit_function; - } - - MmsVariableSpecification* ctlVarSpec = - IedConnection_getVariableSpecification(connection, &error, objectReference, IEC61850_FC_CO); - - if (error != IED_ERROR_OK) { - if (DEBUG_IED_CLIENT) - printf("IED_CLIENT: ControlObjectClient_create: failed to get data directory of control object\n"); - - goto exit_function; - } - /* check what control elements are available */ bool hasOper = false; bool hasTimeActivatedControl = false; @@ -153,8 +122,8 @@ ControlObjectClient_create(const char* objectReference, IedConnection connection MmsVariableSpecification* ctlVal = NULL; MmsVariableSpecification* t = NULL; - if (MmsVariableSpecification_getType(ctlVarSpec) == MMS_STRUCTURE) { - MmsVariableSpecification* oper = MmsVariableSpecification_getNamedVariableRecursive(ctlVarSpec, "Oper"); + if (MmsVariableSpecification_getType(operSpec) == MMS_STRUCTURE) { + MmsVariableSpecification* oper = MmsVariableSpecification_getNamedVariableRecursive(operSpec, "Oper"); if (oper) { @@ -194,8 +163,7 @@ ControlObjectClient_create(const char* objectReference, IedConnection connection if ((ctlVal == NULL) || (t == NULL)) { if (DEBUG_IED_CLIENT) printf("IED_CLIENT: \"Oper\" is missing required element\n"); - - goto free_varspec; + goto exit_function; } self = (ControlObjectClient) GLOBAL_CALLOC(1, sizeof(struct sControlObjectClient)); @@ -230,8 +198,50 @@ ControlObjectClient_create(const char* objectReference, IedConnection connection iedConnection_addControlClient(connection, self); -free_varspec: - MmsVariableSpecification_destroy(ctlVarSpec); +exit_function: + return self; +} + +ControlObjectClient +ControlObjectClient_create(const char* objectReference, IedConnection connection) +{ + ControlObjectClient self = NULL; + + /* request control model from server */ + char reference[129]; + + if (strlen(objectReference) < 121) { + strcpy(reference, objectReference); + strcat(reference, ".ctlModel"); + } + else + goto exit_function; + + IedClientError error; + + uint32_t ctlModel = IedConnection_readUnsigned32Value(connection, &error, reference, IEC61850_FC_CF); + + if (error != IED_ERROR_OK) { + if (DEBUG_IED_CLIENT) + printf("IED_CLIENT: ControlObjectClient_create: failed to get %s from server\n", reference); + + goto exit_function; + } + + MmsVariableSpecification* ctlVarSpec = + IedConnection_getVariableSpecification(connection, &error, objectReference, IEC61850_FC_CO); + + if (error != IED_ERROR_OK) { + if (DEBUG_IED_CLIENT) + printf("IED_CLIENT: ControlObjectClient_create: failed to get data directory of control object\n"); + + goto exit_function; + } + + self = ControlObjectClient_createEx(objectReference, connection, ctlModel, ctlVarSpec); + + if (self == NULL) + MmsVariableSpecification_destroy(ctlVarSpec); exit_function: return self; @@ -267,7 +277,7 @@ ControlObjectClient_setCommandTerminationHandler(ControlObjectClient self, Comma self->commandTerminationHandler = handler; } -char* +const char* ControlObjectClient_getObjectReference(ControlObjectClient self) { return self->objectReference; @@ -279,6 +289,19 @@ ControlObjectClient_getControlModel(ControlObjectClient self) return self->ctlModel; } +void +ControlObjectClient_setControlModel(ControlObjectClient self, ControlModel ctlModel) +{ + self->ctlModel = ctlModel; +} + +void +ControlObjectClient_changeServerControlModel(ControlObjectClient self, ControlModel ctlModel) +{ + //TODO write new ctlModel to server + self->ctlModel = ctlModel; +} + MmsType ControlObjectClient_getCtlValType(ControlObjectClient self) { @@ -346,18 +369,9 @@ createOriginValue(ControlObjectClient self) return origin; } -bool -ControlObjectClient_operate(ControlObjectClient self, MmsValue* ctlVal, uint64_t operTime) +static MmsValue* +prepareOperParameters(ControlObjectClient self, MmsValue* ctlVal, uint64_t operTime) { - bool success = false; - - if (ctlVal == NULL) { - if (DEBUG_IED_CLIENT) - printf("IED_CLIENT: operate - (ctlVal == NULL)!\n"); - - goto exit_function; - } - resetLastApplError(self); MmsValue* operParameters; @@ -443,6 +457,37 @@ ControlObjectClient_operate(ControlObjectClient self, MmsValue* ctlVal, uint64_t strncat(itemId, "$Oper", 64 - controlObjectItemIdLen); + if (DEBUG_IED_CLIENT) + printf("IED_CLIENT: operate: %s/%s\n", domainId, itemId); + + return operParameters; +} + +bool +ControlObjectClient_operate(ControlObjectClient self, MmsValue* ctlVal, uint64_t operTime) +{ + bool success = false; + + if (ctlVal == NULL) { + if (DEBUG_IED_CLIENT) + printf("IED_CLIENT: operate - (ctlVal == NULL)!\n"); + + goto exit_function; + } + + MmsValue* operParameters = prepareOperParameters(self, ctlVal, operTime); + + char domainId[65]; + char itemId[65]; + + MmsMapping_getMmsDomainFromObjectReference(self->objectReference, domainId); + + convertToMmsAndInsertFC(itemId, self->objectReference + strlen(domainId) + 1, "CO"); + + int controlObjectItemIdLen = strlen(itemId); + + strncat(itemId, "$Oper", 64 - controlObjectItemIdLen); + if (DEBUG_IED_CLIENT) printf("IED_CLIENT: operate: %s/%s\n", domainId, itemId); @@ -470,14 +515,65 @@ ControlObjectClient_operate(ControlObjectClient self, MmsValue* ctlVal, uint64_t success = true; - exit_function: +exit_function: return success; } -bool -ControlObjectClient_selectWithValue(ControlObjectClient self, MmsValue* ctlVal) +static void +internalOperateHandler(uint32_t invokeId, void* parameter, MmsError err, MmsDataAccessError accessError) { - resetLastApplError(self); + ControlObjectClient self = (ControlObjectClient) parameter; + + IedConnectionOutstandingCall call = iedConnection_lookupOutstandingCall(self->connection, invokeId); + + if (call) { + + ControlObjectClient_ControlActionHandler handler = (ControlObjectClient_ControlActionHandler) call->callback; + + IedClientError iedError = iedConnection_mapMmsErrorToIedError(err); + + bool success = false; + + if (iedError == IED_ERROR_OK) { + iedError = iedConnection_mapDataAccessErrorToIedError(accessError); + + if (iedError == IED_ERROR_OK) + success = true; + } + + handler(invokeId, call->callbackParameter, iedError, CONTROL_ACTION_TYPE_OPERATE, success); + + iedConnection_releaseOutstandingCall(self->connection, call); + } + else { + if (DEBUG_IED_CLIENT) + printf("IED_CLIENT: internal error - no matching outstanding call (ID: %d)!\n", invokeId); + } +} + +uint32_t +ControlObjectClient_operateAsync(ControlObjectClient self, IedClientError* err, MmsValue* ctlVal, uint64_t operTime, + ControlObjectClient_ControlActionHandler handler, void* parameter) +{ + *err = IED_ERROR_OK; + uint32_t invokeId = 0; + + if (ctlVal == NULL) { + *err = IED_ERROR_USER_PROVIDED_INVALID_ARGUMENT; + goto exit_function; + } + + IedConnectionOutstandingCall call = iedConnection_allocateOutstandingCall(self->connection); + + if (call == NULL) { + *err = IED_ERROR_OUTSTANDING_CALL_LIMIT_REACHED; + goto exit_function; + } + + call->callback = handler; + call->callbackParameter = parameter; + + MmsValue* operParameters = prepareOperParameters(self, ctlVal, operTime); char domainId[65]; char itemId[65]; @@ -486,13 +582,43 @@ ControlObjectClient_selectWithValue(ControlObjectClient self, MmsValue* ctlVal) convertToMmsAndInsertFC(itemId, self->objectReference + strlen(domainId) + 1, "CO"); - strncat(itemId, "$SBOw", 64); + int controlObjectItemIdLen = strlen(itemId); + + strncat(itemId, "$Oper", 64 - controlObjectItemIdLen); if (DEBUG_IED_CLIENT) - printf("IED_CLIENT: select with value: %s/%s\n", domainId, itemId); + printf("IED_CLIENT: operate: %s/%s\n", domainId, itemId); MmsError mmsError; + call->invokeId = MmsConnection_writeVariableAsync(self->connection->connection, &mmsError, domainId, itemId, operParameters, internalOperateHandler, self); + + invokeId = call->invokeId; + + MmsValue_setElement(operParameters, 0, NULL); + MmsValue_delete(operParameters); + + *err = iedConnection_mapMmsErrorToIedError(mmsError); + + if (*err != MMS_ERROR_NONE) { + iedConnection_releaseOutstandingCall(self->connection, call); + } + else { + MmsValue_update(self->ctlVal, ctlVal); + + if (self->analogValue) + MmsValue_setElement(self->analogValue, 0, NULL); + + self->opertime = operTime; + } + +exit_function: + return invokeId; +} + +static MmsValue* +prepareSBOwParameters(ControlObjectClient self, MmsValue* ctlVal) +{ int selValElementCount = 5; if (self->hasTimeActivatedMode) @@ -553,7 +679,39 @@ ControlObjectClient_selectWithValue(ControlObjectClient self, MmsValue* ctlVal) MmsValue_setBitStringBit(check, 0, self->synchroCheck); MmsValue_setElement(selValParameters, index++, check); - MmsConnection_writeVariable(IedConnection_getMmsConnection(self->connection), + return selValParameters; +} + +bool +ControlObjectClient_selectWithValue(ControlObjectClient self, MmsValue* ctlVal) +{ + resetLastApplError(self); + + char domainId[65]; + char itemId[65]; + + MmsMapping_getMmsDomainFromObjectReference(self->objectReference, domainId); + + convertToMmsAndInsertFC(itemId, self->objectReference + strlen(domainId) + 1, "CO"); + + strncat(itemId, "$SBOw", 64); + + if (DEBUG_IED_CLIENT) + printf("IED_CLIENT: select with value: %s/%s\n", domainId, itemId); + + MmsError mmsError; + + int selValElementCount = 5; + + if (self->hasTimeActivatedMode) + selValElementCount++; + + if (self->hasCtlNum) + selValElementCount++; + + MmsValue* selValParameters = prepareSBOwParameters(self, ctlVal); + + MmsDataAccessError writeResult = MmsConnection_writeVariable(IedConnection_getMmsConnection(self->connection), &mmsError, domainId, itemId, selValParameters); MmsValue_setElement(selValParameters, 0, NULL); @@ -564,6 +722,13 @@ ControlObjectClient_selectWithValue(ControlObjectClient self, MmsValue* ctlVal) printf("IED_CLIENT: select-with-value failed!\n"); return false; } + else { + if (writeResult != DATA_ACCESS_ERROR_SUCCESS) { + if (DEBUG_IED_CLIENT) + printf("IED_CLIENT: select-with-value failed!\n"); + return false; + } + } MmsValue_update(self->ctlVal, ctlVal); @@ -573,6 +738,110 @@ ControlObjectClient_selectWithValue(ControlObjectClient self, MmsValue* ctlVal) return true; } +static void +internalSelWithValHandler(uint32_t invokeId, void* parameter, MmsError err, MmsDataAccessError accessError) +{ + ControlObjectClient self = (ControlObjectClient) parameter; + + IedConnectionOutstandingCall call = iedConnection_lookupOutstandingCall(self->connection, invokeId); + + if (call) { + + ControlObjectClient_ControlActionHandler handler = (ControlObjectClient_ControlActionHandler) call->callback; + + IedClientError iedError = iedConnection_mapMmsErrorToIedError(err); + + bool success = false; + + if (iedError == IED_ERROR_OK) { + iedError = iedConnection_mapDataAccessErrorToIedError(accessError); + + if (iedError == IED_ERROR_OK) + success = true; + } + + if (success) { + if (DEBUG_IED_CLIENT) + printf("IED_CLIENT: select-with-value+\n"); + } + else { + if (DEBUG_IED_CLIENT) + printf("IED_CLIENT: select-with-value failed!\n"); + } + + handler(invokeId, call->callbackParameter, iedError, CONTROL_ACTION_TYPE_SELECT, success); + + iedConnection_releaseOutstandingCall(self->connection, call); + } + else { + if (DEBUG_IED_CLIENT) + printf("IED_CLIENT: internal error - no matching outstanding call!\n"); + } +} + +uint32_t +ControlObjectClient_selectWithValueAsync(ControlObjectClient self, IedClientError* err, MmsValue* ctlVal, + ControlObjectClient_ControlActionHandler handler, void* parameter) +{ + *err = IED_ERROR_OK; + uint32_t invokeId = 0; + + if (ctlVal == NULL) { + *err = IED_ERROR_USER_PROVIDED_INVALID_ARGUMENT; + goto exit_function; + } + + IedConnectionOutstandingCall call = iedConnection_allocateOutstandingCall(self->connection); + + if (call == NULL) { + *err = IED_ERROR_OUTSTANDING_CALL_LIMIT_REACHED; + goto exit_function; + } + + MmsValue* selValParameters = prepareSBOwParameters(self, ctlVal); + + resetLastApplError(self); + + char domainId[65]; + char itemId[65]; + + MmsMapping_getMmsDomainFromObjectReference(self->objectReference, domainId); + + convertToMmsAndInsertFC(itemId, self->objectReference + strlen(domainId) + 1, "CO"); + + strncat(itemId, "$SBOw", 64); + + call->callback = handler; + call->callbackParameter = parameter; + + MmsError mmsError; + + if (DEBUG_IED_CLIENT) + printf("IED_CLIENT: select with value: %s/%s\n", domainId, itemId); + + call->invokeId = MmsConnection_writeVariableAsync(self->connection->connection, &mmsError, domainId, itemId, selValParameters, internalSelWithValHandler, self); + + invokeId = call->invokeId; + + MmsValue_setElement(selValParameters, 0, NULL); + MmsValue_delete(selValParameters); + + *err = iedConnection_mapMmsErrorToIedError(mmsError); + + if (*err != MMS_ERROR_NONE) { + iedConnection_releaseOutstandingCall(self->connection, call); + } + else { + MmsValue_update(self->ctlVal, ctlVal); + + if (self->analogValue) + MmsValue_setElement(self->analogValue, 0, NULL); + } + +exit_function: + return invokeId; +} + bool ControlObjectClient_select(ControlObjectClient self) { @@ -631,15 +900,119 @@ ControlObjectClient_select(ControlObjectClient self) MmsValue_delete(value); - exit_function: +exit_function: return selected; } -bool -ControlObjectClient_cancel(ControlObjectClient self) +static void +internalSelectHandler(uint32_t invokeId, void* parameter, MmsError err, MmsValue* value) +{ + ControlObjectClient self = (ControlObjectClient) parameter; + + IedConnectionOutstandingCall call = iedConnection_lookupOutstandingCall(self->connection, invokeId); + + if (call) { + + ControlObjectClient_ControlActionHandler handler = (ControlObjectClient_ControlActionHandler) call->callback; + + IedClientError iedError = iedConnection_mapMmsErrorToIedError(err); + + bool success = false; + + self->ctlNum++; + + if (iedError == IED_ERROR_OK) { + + if (MmsValue_getType(value) == MMS_DATA_ACCESS_ERROR) { + iedError = iedConnection_mapDataAccessErrorToIedError(MmsValue_getDataAccessError(value)); + } + else if (MmsValue_getType(value) == MMS_VISIBLE_STRING) { + + char domainId[65]; + char itemId[65]; + + MmsMapping_getMmsDomainFromObjectReference(self->objectReference, domainId); + + convertToMmsAndInsertFC(itemId, self->objectReference + strlen(domainId) + 1, "CO"); + + strncat(itemId, "$SBO", 64); + + char sboReference[130]; + + snprintf(sboReference, 129, "%s/%s", domainId, itemId); + + if (strcmp(MmsValue_toString(value), "") == 0) { + if (DEBUG_IED_CLIENT) + printf("select-response-\n"); + } + else if (strcmp(MmsValue_toString(value), sboReference) == 0) { + if (DEBUG_IED_CLIENT) + printf("select-response+: (%s)\n", MmsValue_toString(value)); + success = true; + } + else { + if (DEBUG_IED_CLIENT) + printf("IED_CLIENT: select-response: (%s)\n", MmsValue_toString(value)); + } + } + else { + if (DEBUG_IED_CLIENT) + printf("IED_CLIENT: select: unexpected response from server!\n"); + } + } + + handler(invokeId, call->callbackParameter, iedError, CONTROL_ACTION_TYPE_SELECT, success); + + iedConnection_releaseOutstandingCall(self->connection, call); + } + else { + if (DEBUG_IED_CLIENT) + printf("IED_CLIENT: internal error - no matching outstanding call!\n"); + } +} + +uint32_t +ControlObjectClient_selectAsync(ControlObjectClient self, IedClientError* err, ControlObjectClient_ControlActionHandler handler, void* parameter) { resetLastApplError(self); + char domainId[65]; + char itemId[65]; + + MmsMapping_getMmsDomainFromObjectReference(self->objectReference, domainId); + + convertToMmsAndInsertFC(itemId, self->objectReference + strlen(domainId) + 1, "CO"); + + strncat(itemId, "$SBO", 64); + + IedConnectionOutstandingCall call = iedConnection_allocateOutstandingCall(self->connection); + + if (call == NULL) { + *err = IED_ERROR_OUTSTANDING_CALL_LIMIT_REACHED; + return 0; + } + + call->callback = handler; + call->callbackParameter = parameter; + + MmsError mmsError; + + if (DEBUG_IED_CLIENT) + printf("IED_CLIENT: select: %s/%s\n", domainId, itemId); + + call->invokeId = MmsConnection_readVariableAsync(IedConnection_getMmsConnection(self->connection), + &mmsError, domainId, itemId, internalSelectHandler, self); + + if (*err != MMS_ERROR_NONE) { + iedConnection_releaseOutstandingCall(self->connection, call); + } + + return call->invokeId; +} + +static MmsValue* +createCancelParameters(ControlObjectClient self) +{ MmsValue* cancelParameters; if (self->hasTimeActivatedMode) @@ -683,6 +1056,16 @@ ControlObjectClient_cancel(ControlObjectClient self) MmsValue* ctlTest = MmsValue_newBoolean(self->test); MmsValue_setElement(cancelParameters, index++, ctlTest); + return cancelParameters; +} + +bool +ControlObjectClient_cancel(ControlObjectClient self) +{ + resetLastApplError(self); + + MmsValue* cancelParameters = createCancelParameters(self); + char domainId[65]; char itemId[65]; @@ -697,7 +1080,7 @@ ControlObjectClient_cancel(ControlObjectClient self) MmsError mmsError; - MmsConnection_writeVariable(IedConnection_getMmsConnection(self->connection), + MmsDataAccessError writeResult = MmsConnection_writeVariable(IedConnection_getMmsConnection(self->connection), &mmsError, domainId, itemId, cancelParameters); MmsValue_setElement(cancelParameters, 0, NULL); @@ -708,10 +1091,109 @@ ControlObjectClient_cancel(ControlObjectClient self) printf("IED_CLIENT: cancel failed!\n"); return false; } + else { + if (writeResult != DATA_ACCESS_ERROR_SUCCESS) { + if (DEBUG_IED_CLIENT) + printf("IED_CLIENT: cancel failed!\n"); + return false; + } + } return true; } +static void +internalCancelHandler(uint32_t invokeId, void* parameter, MmsError err, MmsDataAccessError accessError) +{ + ControlObjectClient self = (ControlObjectClient) parameter; + + IedConnectionOutstandingCall call = iedConnection_lookupOutstandingCall(self->connection, invokeId); + + if (call) { + + ControlObjectClient_ControlActionHandler handler = (ControlObjectClient_ControlActionHandler) call->callback; + + IedClientError iedError = iedConnection_mapMmsErrorToIedError(err); + + bool success = false; + + if (iedError == IED_ERROR_OK) { + iedError = iedConnection_mapDataAccessErrorToIedError(accessError); + + if (iedError == IED_ERROR_OK) + success = true; + } + + if (success) { + if (DEBUG_IED_CLIENT) + printf("IED_CLIENT: cancel+\n"); + } + else { + if (DEBUG_IED_CLIENT) + printf("IED_CLIENT: cancel failed!\n"); + } + + handler(invokeId, call->callbackParameter, iedError, CONTROL_ACTION_TYPE_CANCEL, success); + + iedConnection_releaseOutstandingCall(self->connection, call); + } + else { + if (DEBUG_IED_CLIENT) + printf("IED_CLIENT: internal error - no matching outstanding call!\n"); + } +} + +uint32_t +ControlObjectClient_cancelAsync(ControlObjectClient self, IedClientError* err, ControlObjectClient_ControlActionHandler handler, void* parameter) +{ + *err = IED_ERROR_OK; + uint32_t invokeId = 0; + + IedConnectionOutstandingCall call = iedConnection_allocateOutstandingCall(self->connection); + + if (call == NULL) { + *err = IED_ERROR_OUTSTANDING_CALL_LIMIT_REACHED; + goto exit_function; + } + + MmsValue* cancelParameters = createCancelParameters(self); + + resetLastApplError(self); + + char domainId[65]; + char itemId[65]; + + MmsMapping_getMmsDomainFromObjectReference(self->objectReference, domainId); + + convertToMmsAndInsertFC(itemId, self->objectReference + strlen(domainId) + 1, "CO"); + + strncat(itemId, "$Cancel", 64); + + call->callback = handler; + call->callbackParameter = parameter; + + MmsError mmsError; + + if (DEBUG_IED_CLIENT) + printf("IED_CLIENT: select with value: %s/%s\n", domainId, itemId); + + call->invokeId = MmsConnection_writeVariableAsync(self->connection->connection, &mmsError, domainId, itemId, cancelParameters, internalCancelHandler, self); + + invokeId = call->invokeId; + + MmsValue_setElement(cancelParameters, 0, NULL); + MmsValue_delete(cancelParameters); + + *err = iedConnection_mapMmsErrorToIedError(mmsError); + + if (*err != MMS_ERROR_NONE) { + iedConnection_releaseOutstandingCall(self->connection, call); + } + +exit_function: + return invokeId; +} + void ControlObjectClient_useConstantT(ControlObjectClient self, bool useConstantT) { diff --git a/src/iec61850/client/ied_connection.c b/src/iec61850/client/ied_connection.c index 96aab4fc..d3c6f922 100644 --- a/src/iec61850/client/ied_connection.c +++ b/src/iec61850/client/ied_connection.c @@ -352,7 +352,7 @@ iedConnection_doesControlObjectMatch(const char* objRef, const char* cntrlObj) } static bool -doesReportMatchControlObject(char* domainName, char* itemName, char* objectRef) +doesReportMatchControlObject(char* domainName, char* itemName, const char* objectRef) { int i = 0; @@ -510,7 +510,7 @@ informationReportHandler(void* parameter, char* domainName, while (control != NULL) { ControlObjectClient object = (ControlObjectClient) control->data; - char* objectRef = ControlObjectClient_getObjectReference(object); + const char* objectRef = ControlObjectClient_getObjectReference(object); if (doesReportMatchControlObject(domainName, variableListName, objectRef)) controlObjectClient_invokeCommandTerminationHandler(object); diff --git a/src/iec61850/inc/iec61850_client.h b/src/iec61850/inc/iec61850_client.h index 87fdb868..abd81340 100644 --- a/src/iec61850/inc/iec61850_client.h +++ b/src/iec61850/inc/iec61850_client.h @@ -1771,11 +1771,32 @@ ClientDataSet_getDataSetSize(ClientDataSet self); typedef struct sControlObjectClient* ControlObjectClient; typedef enum { - CONTROL_MODEL_STATUS_ONLY, - CONTROL_MODEL_DIRECT_NORMAL, - CONTROL_MODEL_SBO_NORMAL, - CONTROL_MODEL_DIRECT_ENHANCED, - CONTROL_MODEL_SBO_ENHANCED + /** + * No support for control functions. Control object only support status information. + */ + CONTROL_MODEL_STATUS_ONLY = 0, + + /** + * Direct control with normal security: Supports Operate, TimeActivatedOperate (optional), + * and Cancel (optional). + */ + CONTROL_MODEL_DIRECT_NORMAL = 1, + + /** + * Select before operate (SBO) with normal security: Supports Select, Operate, TimeActivatedOperate (optional), + * and Cancel (optional). + */ + CONTROL_MODEL_SBO_NORMAL = 2, + + /** + * Direct control with enhanced security (enhanced security includes the CommandTermination service) + */ + CONTROL_MODEL_DIRECT_ENHANCED = 3, + + /** + * Select before operate (SBO) with enhanced security (enhanced security includes the CommandTermination service) + */ + CONTROL_MODEL_SBO_ENHANCED = 4 } ControlModel; @@ -1783,7 +1804,12 @@ typedef enum { * \brief Create a new client control object * * A client control object is used to handle all client side aspects of a controllable - * data object. + * data object. A controllable data object is an instance of a controllable CDC like e.g. + * SPC, DPC, APC, ... + * + * NOTE: This function will synchronously request information about the control object + * (like ctlModel) from the server. The function will block until these requests return + * or time-out. * * \param objectReference the reference of the controllable data object * \param connection the connection instance where the control object has to be reached @@ -1793,15 +1819,68 @@ typedef enum { LIB61850_API ControlObjectClient ControlObjectClient_create(const char* objectReference, IedConnection connection); +/** + * \brief Destroy the client control object instance and release all related resources + * + * \param self the control object instance to use + */ LIB61850_API void ControlObjectClient_destroy(ControlObjectClient self); -LIB61850_API char* +typedef enum +{ + CONTROL_ACTION_TYPE_SELECT = 0, + CONTROL_ACTION_TYPE_OPERATE = 1, + CONTROL_ACTION_TYPE_CANCEL = 2 +} ControlActionType; + + +typedef void +(*ControlObjectClient_ControlActionHandler) (uint32_t invokeId, void* parameter, IedClientError err, ControlActionType type, bool success); + +/** + * \brief Get the object reference of the control data object + * + * \param self the control object instance to use + * + * \return the object reference (string is valid only as long as the \ref ControlObjectClient instance exists). + */ +LIB61850_API const char* ControlObjectClient_getObjectReference(ControlObjectClient self); +/** + * \brief Get the current control model (local representation) applied to the control object + * + * \param self the control object instance to use + * + * \return the current applied control model (\ref ControlModel) + */ LIB61850_API ControlModel ControlObjectClient_getControlModel(ControlObjectClient self); +/** + * \brief Set the applied control model + * + * NOTE: This function call will not change the server control model. + * + * \param self the control object instance to use + * \param ctlModel the new control model to apply + */ +LIB61850_API void +ControlObjectClient_setControlModel(ControlObjectClient self, ControlModel ctlModel); + +/** + * \brief Change the control model of the server. + * + * NOTE: Not supported by all servers. Information can be found in the PIXIT of the server. + * Also sets the applied control model for this client control instance. + * + * \param self the control object instance to use + * \param ctlModel the new control model + */ +LIB61850_API void +ControlObjectClient_changeServerControlModel(ControlObjectClient self, ControlModel ctlModel); + /** * \brief Get the type of ctlVal. * @@ -1867,9 +1946,80 @@ ControlObjectClient_selectWithValue(ControlObjectClient self, MmsValue* ctlVal); LIB61850_API bool ControlObjectClient_cancel(ControlObjectClient self); -LIB61850_API void -ControlObjectClient_setLastApplError(ControlObjectClient self, LastApplError lastAppIError); +/** + * \brief Send an operate command to the server - async version + * + * \param self the control object instance to use + * \param[out] err error code + * \param ctlVal the control value (for APC the value may be either AnalogueValue (MMS_STRUCT) or MMS_FLOAT/MMS_INTEGER + * \param operTime the time when the command has to be executed (for time activated control). If this value is 0 the command will be executed instantly. + * \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 +ControlObjectClient_operateAsync(ControlObjectClient self, IedClientError* err, MmsValue* ctlVal, uint64_t operTime, + ControlObjectClient_ControlActionHandler handler, void* parameter); + +/** + * \brief Send a select command to the server - async version + * + * The select command is only used for the control model "select-before-operate with normal security" + * (CONTROL_MODEL_SBO_NORMAL). The select command has to be sent before the operate command can be used. + * + * \param self the control object instance to use + * \param[out] err error code + * \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 +ControlObjectClient_selectAsync(ControlObjectClient self, IedClientError* err, ControlObjectClient_ControlActionHandler handler, void* parameter); + +/** + * \brief Send a select-with-value command to the server - async version + * + * The select-with-value command is only used for the control model "select-before-operate with enhanced security" + * (CONTROL_MODEL_SBO_ENHANCED). The select-with-value command has to be sent before the operate command can be used. + * + * \param self the control object instance to use + * \param[out] err error code + * \param ctlVal the control value (for APC the value may be either AnalogueValue (MMS_STRUCT) or MMS_FLOAT/MMS_INTEGER + * \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 +ControlObjectClient_selectWithValueAsync(ControlObjectClient self, IedClientError* err, MmsValue* ctlVal, + ControlObjectClient_ControlActionHandler handler, void* parameter); + +/** + * \brief Send a cancel command to the server - async version + * + * The cancel command can be used to stop an ongoing operation (when the server and application + * support this) and to cancel a former select command. + * + * \param self the control object instance to use + * \param[out] err error code + * \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 +ControlObjectClient_cancelAsync(ControlObjectClient self, IedClientError* err, ControlObjectClient_ControlActionHandler handler, void* parameter); + +/** + * \brief Get the last received control application error + * + * NOTE: this is the content of the "LastApplError" message received from the server. + * + * \return the value of the last received application error + */ LIB61850_API LastApplError ControlObjectClient_getLastApplError(ControlObjectClient self); @@ -1891,6 +2041,9 @@ ControlObjectClient_setTestMode(ControlObjectClient self, bool value); * The origin parameter is used to identify the client/application that sent a control * command. It is intended for later analysis. * + * \param self the ControlObjectClient instance + * \param orIdent originator identification can be an arbitrary string + * \param orCat originator category (see \ref ORIGINATOR_CATEGORIES) */ LIB61850_API void ControlObjectClient_setOrigin(ControlObjectClient self, const char* orIdent, int orCat); @@ -1909,13 +2062,13 @@ ControlObjectClient_useConstantT(ControlObjectClient self, bool useConstantT); /** * \deprecated use ControlObjectClient_setInterlockCheck instead */ -LIB61850_API void +LIB61850_API DEPRECATED void ControlObjectClient_enableInterlockCheck(ControlObjectClient self); /** * \deprecated use ControlObjectClient_setSynchroCheck instead */ -LIB61850_API void +LIB61850_API DEPRECATED void ControlObjectClient_enableSynchroCheck(ControlObjectClient self); /** @@ -1944,12 +2097,22 @@ ControlObjectClient_setSynchroCheck(ControlObjectClient self, bool value); * To distinguish between a CommandTermination+ and CommandTermination- please use the * ControlObjectClient_getLastApplError function. * - * \param self the ControlObjectClient instance - * \param handler the callback function to be used - * \param handlerParameter an arbitrary parameter that is passed to the handler + * \param parameter the user paramter that is passed to the callback function + * \param controlClient the ControlObjectClient instance */ typedef void (*CommandTerminationHandler) (void* parameter, ControlObjectClient controlClient); +/** + * \brief Set the command termination callback handler for this control object + * + * This callback is invoked whenever a CommandTermination+ or CommandTermination- message is received. + * To distinguish between a CommandTermination+ and CommandTermination- please use the + * ControlObjectClient_getLastApplError function. + * + * \param self the ControlObjectClient instance + * \param handler the callback function to be used + * \param handlerParameter a user parameter that is passed to the handler + */ LIB61850_API void ControlObjectClient_setCommandTerminationHandler(ControlObjectClient self, CommandTerminationHandler handler, void* handlerParameter); diff --git a/src/iec61850/inc_private/ied_connection_private.h b/src/iec61850/inc_private/ied_connection_private.h index f5eb8964..d81783b8 100644 --- a/src/iec61850/inc_private/ied_connection_private.h +++ b/src/iec61850/inc_private/ied_connection_private.h @@ -122,6 +122,9 @@ ClientReport_destroy(ClientReport self); LIB61850_INTERNAL void controlObjectClient_invokeCommandTerminationHandler(ControlObjectClient self); +LIB61850_INTERNAL void +ControlObjectClient_setLastApplError(ControlObjectClient self, LastApplError lastAppIError); + /* some declarations that are shared with server side ! */ LIB61850_INTERNAL char*