diff --git a/src/iec61850/inc/iec61850_server.h b/src/iec61850/inc/iec61850_server.h index 0ba5d267..77cc75ca 100644 --- a/src/iec61850/inc/iec61850_server.h +++ b/src/iec61850/inc/iec61850_server.h @@ -1211,22 +1211,22 @@ IedServer_setEditSettingGroupConfirmationHandler(IedServer self, SettingGroupCon * \brief result code for ControlPerformCheckHandler */ typedef enum { - CONTROL_ACCEPTED = -1, /** check passed */ - CONTROL_WAITING_FOR_SELECT = 0, /** select operation in progress - handler will be called again later */ - CONTROL_HARDWARE_FAULT = 1, /** check failed due to hardware fault */ - CONTROL_TEMPORARILY_UNAVAILABLE = 2, /** control is already selected or operated */ - CONTROL_OBJECT_ACCESS_DENIED = 3, /** check failed due to access control reason - access denied for this client or state */ - CONTROL_OBJECT_UNDEFINED = 4, /** object not visible in this security context ??? */ - CONTROL_VALUE_INVALID = 11 /** ctlVal out of range */ + CONTROL_ACCEPTED = -1, /**< check passed */ + CONTROL_WAITING_FOR_SELECT = 0, /**< select operation in progress - handler will be called again later */ + CONTROL_HARDWARE_FAULT = 1, /**< check failed due to hardware fault */ + CONTROL_TEMPORARILY_UNAVAILABLE = 2, /**< control is already selected or operated */ + CONTROL_OBJECT_ACCESS_DENIED = 3, /**< check failed due to access control reason - access denied for this client or state */ + CONTROL_OBJECT_UNDEFINED = 4, /**< object not visible in this security context ??? */ + CONTROL_VALUE_INVALID = 11 /**< ctlVal out of range */ } CheckHandlerResult; /** * \brief result codes for control handler (ControlWaitForExecutionHandler and ControlHandler) */ typedef enum { - CONTROL_RESULT_FAILED = 0, /** check or operation failed */ - CONTROL_RESULT_OK = 1, /** check or operation was successful */ - CONTROL_RESULT_WAITING = 2 /** check or operation is in progress */ + CONTROL_RESULT_FAILED = 0, /**< check or operation failed */ + CONTROL_RESULT_OK = 1, /**< check or operation was successful */ + CONTROL_RESULT_WAITING = 2 /**< check or operation is in progress */ } ControlHandlerResult; typedef void* ControlAction; @@ -1387,6 +1387,30 @@ typedef ControlHandlerResult (*ControlWaitForExecutionHandler) (ControlAction ac */ typedef ControlHandlerResult (*ControlHandler) (ControlAction action, void* parameter, MmsValue* ctlVal, bool test); +/** + * \brief Reason why a select state of a control object changed + */ +typedef enum { + SELECT_STATE_REASON_SELECTED, /**< control has been selected */ + SELECT_STATE_REASON_CANCELED, /**< cancel received for the control */ + SELECT_STATE_REASON_TIMEOUT, /**< unselected due to timeout (sboTimeout) */ + SELECT_STATE_REASON_OPERATED, /**< unselected due to successful operate */ + SELECT_STATE_REASON_OPERATE_FAILED, /**< unselected due to failed operate */ + SELECT_STATE_REASON_DISCONNECTED /**< unselected due to disconnection of selecting client */ +} SelectStateChangedReason; + +/** + * \brief Control model callback that is called when the select state of a control changes + * + * New in version 1.5 + * + * \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 isSelected true when the control is selected, false otherwise + * \param reason reason why the select state changed + */ +typedef void (*ControlSelectStateChangedHandler) (ControlAction action, void* parameter, bool isSelected, SelectStateChangedReason reason); + /** * \brief Set control handler for controllable data object * @@ -1437,6 +1461,25 @@ IedServer_setPerformCheckHandler(IedServer self, DataObject* node, ControlPerfor LIB61850_API void IedServer_setWaitForExecutionHandler(IedServer self, DataObject* node, ControlWaitForExecutionHandler handler, void* parameter); + +/** + * \brief Set a callback handler for a controllable data object to track select state changes + * + * The callback is called whenever the select state of a control changes. Reason for changes can be: + * - a successful select or select-with-value by a client + * - select timeout + * - operate or failed operate + * - cancel request by a client + * - the client that selected the control has been disconnected + * + * \param self the instance of IedServer to operate on. + * \param node the controllable data object handle + * \param handler a callback function of type ControlHandler + * \param parameter a user provided parameter that is passed to the control handler. + */ +LIB61850_API void +IedServer_setSelectStateChangedHandler(IedServer self, DataObject* node, ControlSelectStateChangedHandler handler, void* parameter); + /** * \brief Update the control model for the specified controllable data object with the given value and * update "ctlModel" attribute value. diff --git a/src/iec61850/inc_private/control.h b/src/iec61850/inc_private/control.h index 3ea5f337..2c57f7f7 100644 --- a/src/iec61850/inc_private/control.h +++ b/src/iec61850/inc_private/control.h @@ -129,6 +129,9 @@ struct sControlObject ControlWaitForExecutionHandler waitForExecutionHandler; void* waitForExecutionHandlerParameter; + ControlSelectStateChangedHandler selectStateChangedHandler; + void* selectStateChangedHandlerParameter; + #if (CONFIG_IEC61850_SERVICE_TRACKING == 1) /* Common data class (CDC) of control object */ ControlServiceCDC cdc; @@ -182,6 +185,10 @@ ControlObject_installCheckHandler(ControlObject* self, ControlPerformCheckHandle LIB61850_INTERNAL void ControlObject_installWaitForExecutionHandler(ControlObject* self, ControlWaitForExecutionHandler handler, void* parameter); +LIB61850_INTERNAL void +ControlObject_installSelectStateChangedHandler(ControlObject* self, ControlSelectStateChangedHandler handler, + void* parameter); + LIB61850_INTERNAL void ControlObject_updateControlModel(ControlObject* self, ControlModel value, DataObject* ctlObject); diff --git a/src/iec61850/server/impl/ied_server.c b/src/iec61850/server/impl/ied_server.c index ae95ab44..ccf6e3d0 100644 --- a/src/iec61850/server/impl/ied_server.c +++ b/src/iec61850/server/impl/ied_server.c @@ -853,7 +853,7 @@ IedServer_setControlHandler( { ControlObject* controlObject = lookupControlObject(self, node); - if (controlObject != NULL) { + if (controlObject) { ControlObject_installListener(controlObject, listener, parameter); if (DEBUG_IED_SERVER) printf("IED_SERVER: Installed control handler for %s!\n", node->name); @@ -868,7 +868,7 @@ IedServer_setPerformCheckHandler(IedServer self, DataObject* node, ControlPerfor { ControlObject* controlObject = lookupControlObject(self, node); - if (controlObject != NULL) + if (controlObject) ControlObject_installCheckHandler(controlObject, handler, parameter); } @@ -878,10 +878,20 @@ IedServer_setWaitForExecutionHandler(IedServer self, DataObject* node, ControlWa { ControlObject* controlObject = lookupControlObject(self, node); - if (controlObject != NULL) + if (controlObject) ControlObject_installWaitForExecutionHandler(controlObject, handler, parameter); } +void +IedServer_setSelectStateChangedHandler(IedServer self, DataObject* node, ControlSelectStateChangedHandler handler, + void* parameter) +{ + ControlObject* controlObject = lookupControlObject(self, node); + + if (controlObject) + ControlObject_installSelectStateChangedHandler(controlObject, handler, parameter); +} + void IedServer_updateCtlModel(IedServer self, DataObject* ctlObject, ControlModel value) { diff --git a/src/iec61850/server/mms_mapping/control.c b/src/iec61850/server/mms_mapping/control.c index a2964c65..41137f66 100644 --- a/src/iec61850/server/mms_mapping/control.c +++ b/src/iec61850/server/mms_mapping/control.c @@ -408,7 +408,7 @@ updateGenericTrackingObjectValues(MmsMapping* self, ControlObject* controlObject #endif /* (CONFIG_IEC61850_SERVICE_TRACKING == 1) */ static void -unselectObject(ControlObject* self); +unselectObject(ControlObject* self, SelectStateChangedReason reason); static void setState(ControlObject* self, int newState) @@ -488,17 +488,33 @@ selectObject(ControlObject* self, uint64_t selectTime, MmsServerConnection conne self->mmsConnection = connection; setStSeld(self, true); setState(self, STATE_READY); + + if (self->selectStateChangedHandler) { + self->selectStateChangedHandler((ControlAction) self, + self->selectStateChangedHandlerParameter, + true, + SELECT_STATE_REASON_SELECTED); + } } static void -unselectObject(ControlObject* self) +unselectObject(ControlObject* self, SelectStateChangedReason reason) { - setState(self, STATE_UNSELECTED); + if (getState(self) != STATE_UNSELECTED) { + setState(self, STATE_UNSELECTED); - setStSeld(self, false); + setStSeld(self, false); - if (DEBUG_IED_SERVER) - printf("IED_SERVER: control %s/%s.%s unselected\n", MmsDomain_getName(self->mmsDomain), self->lnName, self->name); + if (self->selectStateChangedHandler) { + self->selectStateChangedHandler((ControlAction) self, + self->selectStateChangedHandlerParameter, + false, + reason); + } + + if (DEBUG_IED_SERVER) + printf("IED_SERVER: control %s/%s.%s unselected\n", MmsDomain_getName(self->mmsDomain), self->lnName, self->name); + } } static void @@ -513,7 +529,7 @@ checkSelectTimeout(ControlObject* self, uint64_t currentTime) printf("IED_SERVER: select-timeout (timeout-val = %i) for control %s/%s.%s\n", self->selectTimeout, MmsDomain_getName(self->mmsDomain), self->lnName, self->name); - unselectObject(self); + unselectObject(self, SELECT_STATE_REASON_TIMEOUT); } } } @@ -620,16 +636,16 @@ exitControlTask(ControlObject* self) } static void -abortControlOperation(ControlObject* self, bool unconditional) +abortControlOperation(ControlObject* self, bool unconditional, SelectStateChangedReason reason) { if ((self->ctlModel == 2) || (self->ctlModel == 4)) { if (unconditional) { - unselectObject(self); + unselectObject(self, reason); } else { if (isSboClassOperateOnce(self)) - unselectObject(self); + unselectObject(self, reason); else setState(self, STATE_READY); } @@ -804,7 +820,7 @@ executeStateMachine: resetAddCause(controlObject); - abortControlOperation(controlObject, false); + abortControlOperation(controlObject, false, SELECT_STATE_REASON_OPERATE_FAILED); exitControlTask(controlObject); } else if (dynamicCheckResult == CONTROL_RESULT_OK) { @@ -854,6 +870,8 @@ executeStateMachine: updateGenericTrackingObjectValues(self, controlObject, IEC61850_SERVICE_TYPE_COMMAND_TERMINATION, IEC61850_SERVICE_ERROR_NO_ERROR); #endif /* (CONFIG_IEC61850_SERVICE_TRACKING == 1) */ } + + abortControlOperation(controlObject, false, SELECT_STATE_REASON_OPERATED); } else { @@ -867,9 +885,10 @@ executeStateMachine: #if (CONFIG_IEC61850_SERVICE_TRACKING == 1) updateGenericTrackingObjectValues(self, controlObject, IEC61850_SERVICE_TYPE_COMMAND_TERMINATION, IEC61850_SERVICE_ERROR_FAILED_DUE_TO_SERVER_CONSTRAINT); #endif /* (CONFIG_IEC61850_SERVICE_TRACKING == 1) */ + + abortControlOperation(controlObject, false, SELECT_STATE_REASON_OPERATE_FAILED); } - abortControlOperation(controlObject, false); exitControlTask(controlObject); setOpOk(controlObject, false, currentTimeInMs); @@ -1292,7 +1311,7 @@ bool ControlObject_unselect(ControlObject* self, MmsServerConnection connection) { if (self->mmsConnection == connection) { - abortControlOperation(self, true); + abortControlOperation(self, true, SELECT_STATE_REASON_DISCONNECTED); return true; } else @@ -1321,6 +1340,14 @@ ControlObject_installWaitForExecutionHandler(ControlObject* self, ControlWaitFor self->waitForExecutionHandlerParameter = parameter; } +void +ControlObject_installSelectStateChangedHandler(ControlObject* self, ControlSelectStateChangedHandler handler, + void* parameter) +{ + self->selectStateChangedHandler = handler; + self->selectStateChangedHandlerParameter = parameter; +} + void ControlObject_updateControlModel(ControlObject* self, ControlModel value, DataObject* ctlObject) { @@ -1394,7 +1421,7 @@ Control_processControlActions(MmsMapping* self, uint64_t currentTimeInMs) /* leave state Perform Test */ setOpRcvd(controlObject, false); - abortControlOperation(controlObject, false); + abortControlOperation(controlObject, false, SELECT_STATE_REASON_OPERATE_FAILED); resetAddCause(controlObject); } @@ -2056,7 +2083,7 @@ Control_writeAccessControlObject(MmsMapping* self, MmsDomain* domain, char* vari CONTROL_ERROR_NO_ERROR, ADD_CAUSE_INCONSISTENT_PARAMETERS, ctlNum, origin, true); - unselectObject(controlObject); + unselectObject(controlObject, SELECT_STATE_REASON_OPERATE_FAILED); } goto free_and_return; @@ -2112,7 +2139,7 @@ Control_writeAccessControlObject(MmsMapping* self, MmsDomain* domain, char* vari CONTROL_ERROR_NO_ERROR, ADD_CAUSE_INCONSISTENT_PARAMETERS, ctlNum, origin, true); - unselectObject(controlObject); + unselectObject(controlObject, SELECT_STATE_REASON_OPERATE_FAILED); goto free_and_return; } @@ -2197,10 +2224,7 @@ Control_writeAccessControlObject(MmsMapping* self, MmsDomain* domain, char* vari /* leave state Perform Test */ setOpRcvd(controlObject, false); - abortControlOperation(controlObject, false); - - if ((controlObject->ctlModel == 2) || (controlObject->ctlModel == 4)) - unselectObject(controlObject); + abortControlOperation(controlObject, false, SELECT_STATE_REASON_OPERATE_FAILED); } } @@ -2254,7 +2278,7 @@ Control_writeAccessControlObject(MmsMapping* self, MmsDomain* domain, char* vari if (state != STATE_UNSELECTED) { if (controlObject->mmsConnection == connection) { indication = DATA_ACCESS_ERROR_SUCCESS; - unselectObject(controlObject); + unselectObject(controlObject, SELECT_STATE_REASON_CANCELED); goto free_and_return; } else { @@ -2271,7 +2295,7 @@ Control_writeAccessControlObject(MmsMapping* self, MmsDomain* domain, char* vari if (controlObject->timeActivatedOperate) { controlObject->timeActivatedOperate = false; - abortControlOperation(controlObject, false); + abortControlOperation(controlObject, false, SELECT_STATE_REASON_CANCELED); indication = DATA_ACCESS_ERROR_SUCCESS;