From 6c14425ca80e1bee16da8e7b66ffb8bbd9bfcd0f Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Thu, 25 Apr 2019 11:12:24 +0200 Subject: [PATCH] - IEC 61850 server: refactored control model API (changed handler signatures, added ControlAction object to access origin and set addCause value, ...) - IEC 61850 client: added ControlObjectClient_getLastError function --- .../server_example_control.c | 5 +- src/iec61850/client/client_control.c | 15 +++ src/iec61850/inc/iec61850_client.h | 10 ++ src/iec61850/inc/iec61850_server.h | 70 ++++++++++- src/iec61850/inc_private/control.h | 5 +- src/iec61850/server/impl/ied_server.c | 3 + src/iec61850/server/mms_mapping/control.c | 119 +++++++++++++----- 7 files changed, 185 insertions(+), 42 deletions(-) diff --git a/examples/server_example_control/server_example_control.c b/examples/server_example_control/server_example_control.c index b5e2d498..c08fcd30 100644 --- a/examples/server_example_control/server_example_control.c +++ b/examples/server_example_control/server_example_control.c @@ -25,7 +25,7 @@ sigint_handler(int signalId) } static CheckHandlerResult -checkHandler(void* parameter, MmsValue* ctlVal, bool test, bool interlockCheck, ClientConnection connection) +checkHandler(ControlAction action, void* parameter, MmsValue* ctlVal, bool test, bool interlockCheck) { printf("check handler called!\n"); @@ -51,7 +51,7 @@ checkHandler(void* parameter, MmsValue* ctlVal, bool test, bool interlockCheck, } static ControlHandlerResult -controlHandlerForBinaryOutput(void* parameter, MmsValue* value, bool test) +controlHandlerForBinaryOutput(ControlAction action, void* parameter, MmsValue* value, bool test) { uint64_t timestamp = Hal_getTimeInMs(); @@ -102,7 +102,6 @@ writeAccessHandler (DataAttribute* dataAttribute, MmsValue* value, ClientConnect int main(int argc, char** argv) { - iedServer = IedServer_create(&iedModel); IedServer_setControlHandler(iedServer, IEDMODEL_GenericIO_GGIO1_SPCSO1, diff --git a/src/iec61850/client/client_control.c b/src/iec61850/client/client_control.c index 5f584e4b..ebe24319 100644 --- a/src/iec61850/client/client_control.c +++ b/src/iec61850/client/client_control.c @@ -56,6 +56,7 @@ struct sControlObjectClient uint64_t constantT; /* timestamp of select/operate to be used when constant T option is selected */ LastApplError lastApplError; + MmsError lastMmsError; CommandTerminationHandler commandTerminationHandler; void* commandTerminaionHandlerParameter; @@ -312,6 +313,12 @@ ControlObjectClient_getCtlValType(ControlObjectClient self) return MmsValue_getType(self->ctlVal); } +IedClientError +ControlObjectClient_getLastError(ControlObjectClient self) +{ + return iedConnection_mapMmsErrorToIedError(self->lastMmsError); +} + void ControlObjectClient_setOrigin(ControlObjectClient self, const char* orIdent, int orCat) { @@ -500,6 +507,8 @@ ControlObjectClient_operate(ControlObjectClient self, MmsValue* ctlVal, uint64_t MmsValue_setElement(operParameters, 0, NULL); MmsValue_delete(operParameters); + self->lastMmsError = mmsError; + if (mmsError != MMS_ERROR_NONE) { if (DEBUG_IED_CLIENT) printf("IED_CLIENT: operate failed!\n"); @@ -718,6 +727,8 @@ ControlObjectClient_selectWithValue(ControlObjectClient self, MmsValue* ctlVal) MmsValue_setElement(selValParameters, 0, NULL); MmsValue_delete(selValParameters); + self->lastMmsError = mmsError; + if (mmsError != MMS_ERROR_NONE) { if (DEBUG_IED_CLIENT) printf("IED_CLIENT: select-with-value failed!\n"); @@ -869,6 +880,8 @@ ControlObjectClient_select(ControlObjectClient self) self->ctlNum++; + self->lastMmsError = mmsError; + if (value == NULL) { if (DEBUG_IED_CLIENT) printf("IED_CLIENT: select: read SBO failed!\n"); @@ -1068,6 +1081,8 @@ ControlObjectClient_cancel(ControlObjectClient self) MmsDataAccessError writeResult = MmsConnection_writeVariable(IedConnection_getMmsConnection(self->connection), &mmsError, domainId, itemId, cancelParameters); + self->lastMmsError = mmsError; + MmsValue_setElement(cancelParameters, 0, NULL); MmsValue_delete(cancelParameters); diff --git a/src/iec61850/inc/iec61850_client.h b/src/iec61850/inc/iec61850_client.h index 650c88ad..340ee381 100644 --- a/src/iec61850/inc/iec61850_client.h +++ b/src/iec61850/inc/iec61850_client.h @@ -2001,6 +2001,16 @@ ControlObjectClient_changeServerControlModel(ControlObjectClient self, ControlMo LIB61850_API MmsType ControlObjectClient_getCtlValType(ControlObjectClient self); +/** + * \brief Get the error code of the last synchronous control action (operate, select, select-with-value, cancel) + * + * \param self the control object instance to use + * + * \return the client error code + */ +LIB61850_API IedClientError +ControlObjectClient_getLastError(ControlObjectClient self); + /** * \brief Send an operate command to the server * diff --git a/src/iec61850/inc/iec61850_server.h b/src/iec61850/inc/iec61850_server.h index cc46604f..cd4336fb 100644 --- a/src/iec61850/inc/iec61850_server.h +++ b/src/iec61850/inc/iec61850_server.h @@ -3,7 +3,7 @@ * * IEC 61850 server API for libiec61850. * - * Copyright 2013-2018 Michael Zillgith + * Copyright 2013-2019 Michael Zillgith * * This file is part of libIEC61850. * @@ -1083,9 +1083,62 @@ typedef enum { CONTROL_RESULT_WAITING = 2 /** check or operation is in progress */ } ControlHandlerResult; +typedef void* ControlAction; + +/** + * \brief Set the add cause for the next command termination or application error message + * + * \param self the control action instance + * \param addCause the additional cause + */ +LIB61850_API void +ControlAction_setAddCause(ControlAction self, ControlAddCause addCause); + +/** + * \brief Get the originator category provided by the client + * + * \param self the control action instance + * + * \return the originator category + */ +LIB61850_API int +ControlAction_getOrCat(ControlAction self); + +/** + * \brief Get the originator identifier provided by the client + * + * \param self the control action instance + * + * \return the originator identifier + */ +LIB61850_API uint8_t* +ControlAction_getOrIdent(ControlAction self, int* orIdentSize); + +/** + * \brief Get the client object associated with the client that caused the control action + * + * \param self the control action instance + * + * \return client connection instance + */ +LIB61850_API ClientConnection +ControlAction_getClientConnection(ControlAction self); + +/** + * \brief Get the control object that is subject to this action + * + * \param self the control action instance + * + * \return the controllable data object instance + */ +LIB61850_API DataObject* +ControlAction_getControlObject(ControlAction self); + /** * \brief Control model callback to perform the static tests (optional). * + * NOTE: Signature changed in version 1.4! + * * User provided callback function for the control model. It will be invoked after * a control operation has been invoked by the client. This callback function is * intended to perform the static tests. It should check if the interlock conditions @@ -1093,20 +1146,21 @@ typedef enum { * This handler can also be check if the client has the required permissions to execute the * operation and allow or deny the operation accordingly. * + * \param action the control action parameter that provides access to additional context information * \param parameter the parameter that was specified when setting the control handler * \param ctlVal the control value of the control operation. * \param test indicates if the operate request is a test operation * \param interlockCheck the interlockCheck parameter provided by the client - * \param connection the connection object of the client connection that invoked the control operation * * \return CONTROL_ACCEPTED if the static tests had been successful, one of the error codes otherwise */ -typedef CheckHandlerResult (*ControlPerformCheckHandler) (void* parameter, MmsValue* ctlVal, bool test, bool interlockCheck, - ClientConnection connection); +typedef CheckHandlerResult (*ControlPerformCheckHandler) (ControlAction action, void* parameter, MmsValue* ctlVal, bool test, bool interlockCheck); /** * \brief Control model callback to perform the dynamic tests (optional). * + * NOTE: Signature changed in version 1.4! + * * User provided callback function for the control model. It will be invoked after * a control operation has been invoked by the client. This callback function is * intended to perform the dynamic tests. It should check if the synchronization conditions @@ -1115,6 +1169,7 @@ typedef CheckHandlerResult (*ControlPerformCheckHandler) (void* parameter, MmsVa * cannot be performed immediately the function SHOULD return CONTROL_RESULT_WAITING and the * handler will be invoked again later. * + * \param action the control action parameter that provides access to additional context information * \param parameter the parameter that was specified when setting the control handler * \param ctlVal the control value of the control operation. * \param test indicates if the operate request is a test operation @@ -1123,11 +1178,13 @@ typedef CheckHandlerResult (*ControlPerformCheckHandler) (void* parameter, MmsVa * \return CONTROL_RESULT_OK if the dynamic tests had been successful, CONTROL_RESULT_FAILED otherwise, * CONTROL_RESULT_WAITING if the test is not yet finished */ -typedef ControlHandlerResult (*ControlWaitForExecutionHandler) (void* parameter, MmsValue* ctlVal, bool test, bool synchroCheck); +typedef ControlHandlerResult (*ControlWaitForExecutionHandler) (ControlAction action, void* parameter, MmsValue* ctlVal, bool test, bool synchroCheck); /** * \brief Control model callback to actually perform the control operation. * + * NOTE: Signature changed in version 1.4! + * * User provided callback function for the control model. It will be invoked when * a control operation happens (Oper). Here the user should perform the control operation * (e.g. by setting an digital output or switching a relay). @@ -1135,6 +1192,7 @@ typedef ControlHandlerResult (*ControlWaitForExecutionHandler) (void* parameter, * cannot be performed immediately the function SHOULD return CONTROL_RESULT_WAITING and the * handler will be invoked again later. * + * \param action the control action parameter that provides access to additional context information * \param parameter the parameter that was specified when setting the control handler * \param ctlVal the control value of the control operation. * \param test indicates if the operate request is a test operation @@ -1142,7 +1200,7 @@ typedef ControlHandlerResult (*ControlWaitForExecutionHandler) (void* parameter, * \return CONTROL_RESULT_OK if the control action bas been successful, CONTROL_RESULT_FAILED otherwise, * CONTROL_RESULT_WAITING if the test is not yet finished */ -typedef ControlHandlerResult (*ControlHandler) (void* parameter, MmsValue* ctlVal, bool test); +typedef ControlHandlerResult (*ControlHandler) (ControlAction action, void* parameter, MmsValue* ctlVal, bool test); /** * \brief Set control handler for controllable data object diff --git a/src/iec61850/inc_private/control.h b/src/iec61850/inc_private/control.h index f3931982..78b5a3f1 100644 --- a/src/iec61850/inc_private/control.h +++ b/src/iec61850/inc_private/control.h @@ -51,6 +51,7 @@ struct sControlObject int synchroCheck:1; int timeActivatedOperate:1; int operateOnce:1; + ControlAddCause addCauseValue:6; #if (CONFIG_MMS_THREADLESS_STACK != 1) Semaphore stateLock; @@ -85,8 +86,6 @@ struct sControlObject /* for automatic update of tOpOk attribute */ DataAttribute* tOpOk; -// char ctlObjectName[130]; - /* for LastAppIError */ MmsValue* error; MmsValue* addCause; @@ -110,6 +109,8 @@ struct sControlObject ControlWaitForExecutionHandler waitForExecutionHandler; void* waitForExecutionHandlerParameter; + + DataObject* dataObject; }; LIB61850_INTERNAL ControlObject* diff --git a/src/iec61850/server/impl/ied_server.c b/src/iec61850/server/impl/ied_server.c index 5cf79de2..748fee1e 100644 --- a/src/iec61850/server/impl/ied_server.c +++ b/src/iec61850/server/impl/ied_server.c @@ -751,6 +751,9 @@ lookupControlObject(IedServer self, DataObject* node) ControlObject* controlObject = MmsMapping_getControlObject(self->mmsMapping, domain, lnName, objectName); + if (controlObject) + controlObject->dataObject = node; + return controlObject; } diff --git a/src/iec61850/server/mms_mapping/control.c b/src/iec61850/server/mms_mapping/control.c index eafac088..b5b95ed0 100644 --- a/src/iec61850/server/mms_mapping/control.c +++ b/src/iec61850/server/mms_mapping/control.c @@ -261,8 +261,10 @@ operateControl(ControlObject* self, MmsValue* value, uint64_t currentTime, bool { self->selectTime = currentTime; + self->addCauseValue = ADD_CAUSE_UNKNOWN; + if (self->operateHandler != NULL) - return self->operateHandler(self->operateHandlerParameter, value, testCondition); + return self->operateHandler((ControlAction) self, self->operateHandlerParameter, value, testCondition); return CONTROL_RESULT_OK; } @@ -287,15 +289,17 @@ executeStateMachine: if (state == STATE_WAIT_FOR_ACTIVATION_TIME) isTimeActivatedControl = true; + self->addCauseValue = ADD_CAUSE_BLOCKED_BY_SYNCHROCHECK; + if (self->waitForExecutionHandler != NULL) { - dynamicCheckResult = self->waitForExecutionHandler(self->waitForExecutionHandlerParameter, self->ctlVal, + dynamicCheckResult = self->waitForExecutionHandler((ControlAction) self, self->waitForExecutionHandlerParameter, self->ctlVal, self->testMode, self->synchroCheck); } if (dynamicCheckResult == CONTROL_RESULT_FAILED) { if (isTimeActivatedControl) { ControlObject_sendLastApplError(self, self->mmsConnection, "Oper", - CONTROL_ERROR_NO_ERROR, ADD_CAUSE_BLOCKED_BY_SYNCHROCHECK, + CONTROL_ERROR_NO_ERROR, self->addCauseValue, self->ctlNum, self->origin, false); } else @@ -795,12 +799,11 @@ Control_processControlActions(MmsMapping* self, uint64_t currentTimeInMs) if (controlObject->checkHandler != NULL) { /* perform operative tests */ - ClientConnection clientConnection = private_IedServer_getClientConnectionByHandle(self->iedServer, - controlObject->mmsConnection); + controlObject->addCauseValue = ADD_CAUSE_BLOCKED_BY_INTERLOCKING; - checkResult = controlObject->checkHandler( + checkResult = controlObject->checkHandler((ControlAction) self, controlObject->checkHandlerParameter, controlObject->ctlVal, controlObject->testMode, - controlObject->interlockCheck, clientConnection); + controlObject->interlockCheck); } if (checkResult == CONTROL_ACCEPTED) { @@ -814,9 +817,10 @@ Control_processControlActions(MmsMapping* self, uint64_t currentTimeInMs) executeControlTask(controlObject, currentTimeInMs); } else { + ControlObject_sendLastApplError(controlObject, controlObject->mmsConnection, "Oper", - CONTROL_ERROR_NO_ERROR, ADD_CAUSE_BLOCKED_BY_INTERLOCKING, - controlObject->ctlNum, controlObject->origin, false); + CONTROL_ERROR_NO_ERROR, controlObject->addCauseValue, + controlObject->ctlNum, controlObject->origin, false); /* leave state Perform Test */ setOpRcvd(controlObject, false); @@ -1053,7 +1057,7 @@ ControlObject_sendCommandTerminationNegative(ControlObject* self) MmsValue_setElement(lastApplError, 0, ctlObjValue); MmsValue_setInt32(self->error, CONTROL_ERROR_UNKOWN); - MmsValue_setInt32(self->addCause, ADD_CAUSE_UNKNOWN); + MmsValue_setInt32(self->addCause, self->addCauseValue); MmsValue_setElement(lastApplError, 1, self->error); MmsValue_setElement(lastApplError, 2, self->origin); @@ -1262,14 +1266,12 @@ Control_readAccessControlObject(MmsMapping* self, MmsDomain* domain, char* varia /* opRcvd must not be set here! */ - if (controlObject->checkHandler != NULL) { /* perform operative tests */ + controlObject->addCauseValue = ADD_CAUSE_UNKNOWN; - ClientConnection clientConnection = private_IedServer_getClientConnectionByHandle(self->iedServer, - connection); + if (controlObject->checkHandler != NULL) { /* perform operative tests */ - checkResult = controlObject->checkHandler( - controlObject->checkHandlerParameter, NULL, false, false, - clientConnection); + checkResult = controlObject->checkHandler((ControlAction) controlObject, + controlObject->checkHandlerParameter, NULL, false, false); } if (checkResult == CONTROL_ACCEPTED) { @@ -1442,10 +1444,10 @@ Control_writeAccessControlObject(MmsMapping* self, MmsDomain* domain, char* vari indication = DATA_ACCESS_ERROR_TEMPORARILY_UNAVAILABLE; if (connection != controlObject->mmsConnection) - ControlObject_sendLastApplError(controlObject, connection, "SBOw", 0, + ControlObject_sendLastApplError(controlObject, connection, "SBOw", CONTROL_ERROR_NO_ERROR, ADD_CAUSE_LOCKED_BY_OTHER_CLIENT, ctlNum, origin, true); else - ControlObject_sendLastApplError(controlObject, connection, "SBOw", 0, + ControlObject_sendLastApplError(controlObject, connection, "SBOw", CONTROL_ERROR_NO_ERROR, ADD_CAUSE_OBJECT_ALREADY_SELECTED, ctlNum, origin, true); if (DEBUG_IED_SERVER) @@ -1461,14 +1463,12 @@ Control_writeAccessControlObject(MmsMapping* self, MmsDomain* domain, char* vari bool testCondition = MmsValue_getBoolean(test); - if (controlObject->checkHandler != NULL) { /* perform operative tests */ + controlObject->addCauseValue = ADD_CAUSE_SELECT_FAILED; - ClientConnection clientConnection = private_IedServer_getClientConnectionByHandle(self->iedServer, - connection); + if (controlObject->checkHandler != NULL) { /* perform operative tests */ - checkResult = controlObject->checkHandler( - controlObject->checkHandlerParameter, ctlVal, testCondition, interlockCheck, - clientConnection); + checkResult = controlObject->checkHandler((ControlAction) controlObject, + controlObject->checkHandlerParameter, ctlVal, testCondition, interlockCheck); } if (checkResult == CONTROL_ACCEPTED) { @@ -1485,7 +1485,7 @@ Control_writeAccessControlObject(MmsMapping* self, MmsDomain* domain, char* vari indication = (MmsDataAccessError) checkResult; ControlObject_sendLastApplError(controlObject, connection, "SBOw", 0, - ADD_CAUSE_SELECT_FAILED, ctlNum, origin, true); + controlObject->addCauseValue, ctlNum, origin, true); if (DEBUG_IED_SERVER) printf("SBOw: select rejected by application!\n"); @@ -1601,14 +1601,12 @@ Control_writeAccessControlObject(MmsMapping* self, MmsDomain* domain, char* vari /* enter state Perform Test */ setOpRcvd(controlObject, true); - if (controlObject->checkHandler != NULL) { /* perform operative tests */ + controlObject->addCauseValue = ADD_CAUSE_UNKNOWN; - ClientConnection clientConnection = private_IedServer_getClientConnectionByHandle(self->iedServer, - connection); + if (controlObject->checkHandler != NULL) { /* perform operative tests */ - checkResult = controlObject->checkHandler( - controlObject->checkHandlerParameter, ctlVal, testCondition, interlockCheck, - clientConnection); + checkResult = controlObject->checkHandler((ControlAction) controlObject, + controlObject->checkHandlerParameter, ctlVal, testCondition, interlockCheck); } if (checkResult == CONTROL_ACCEPTED) { @@ -1693,5 +1691,64 @@ free_and_return: return indication; } +void +ControlAction_setAddCause(ControlAction self, ControlAddCause addCause) +{ + ControlObject* controlObject = (ControlObject*) self; + + controlObject->addCauseValue = addCause; +} + +int +ControlAction_getOrCat(ControlAction self) +{ + ControlObject* controlObject = (ControlObject*) self; + + if (controlObject->origin) { + MmsValue* orCat = MmsValue_getElement(controlObject->origin, 0); + + if (orCat) { + return MmsValue_toInt32(orCat); + } + } + + return 0; +} + +uint8_t* +ControlAction_getOrIdent(ControlAction self, int* orIdentSize) +{ + ControlObject* controlObject = (ControlObject*) self; + + if (controlObject->origin) { + MmsValue* orIdent = MmsValue_getElement(controlObject->origin, 1); + + if (orIdent) { + if (MmsValue_getType(orIdent) == MMS_OCTET_STRING) { + *orIdentSize = MmsValue_getOctetStringSize(orIdent); + return MmsValue_getOctetStringBuffer(orIdent); + } + } + } + + return NULL; +} + +ClientConnection +ControlAction_getClientConnection(ControlAction self) +{ + ControlObject* controlObject = (ControlObject*) self; + + return private_IedServer_getClientConnectionByHandle(controlObject->iedServer, controlObject->mmsConnection); +} + +DataObject* +ControlAction_getControlObject(ControlAction self) +{ + ControlObject* controlObject = (ControlObject*) self; + + return controlObject->dataObject; +} + #endif /* (CONFIG_IEC61850_CONTROL_SERVICE == 1) */