- IEC 61850 Client: ControlObjectClient - added async control actions

pull/93/head
Michael Zillgith 7 years ago
parent 1cc350ed4a
commit 43e0fb4d05

@ -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) { int main(int argc, char** argv) {
char* hostname; char* hostname;
@ -259,6 +265,28 @@ int main(int argc, char** argv) {
} }
LinkedList_destroyDeep(values, (LinkedListValueDeleteFunction) MmsValue_delete); 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); Thread_sleep(1000);

@ -1,7 +1,7 @@
/* /*
* client_control.c * client_control.c
* *
* Copyright 2013, 2014 Michael Zillgith * Copyright 2013-2018 Michael Zillgith
* *
* This file is part of libIEC61850. * This file is part of libIEC61850.
* *
@ -110,41 +110,10 @@ resetLastApplError(ControlObjectClient self)
} }
ControlObjectClient ControlObjectClient
ControlObjectClient_create(const char* objectReference, IedConnection connection) ControlObjectClient_createEx(const char* objectReference, IedConnection connection, uint32_t ctlModel, MmsVariableSpecification* operSpec)
{ {
ControlObjectClient self = NULL; 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 */ /* check what control elements are available */
bool hasOper = false; bool hasOper = false;
bool hasTimeActivatedControl = false; bool hasTimeActivatedControl = false;
@ -153,8 +122,8 @@ ControlObjectClient_create(const char* objectReference, IedConnection connection
MmsVariableSpecification* ctlVal = NULL; MmsVariableSpecification* ctlVal = NULL;
MmsVariableSpecification* t = NULL; MmsVariableSpecification* t = NULL;
if (MmsVariableSpecification_getType(ctlVarSpec) == MMS_STRUCTURE) { if (MmsVariableSpecification_getType(operSpec) == MMS_STRUCTURE) {
MmsVariableSpecification* oper = MmsVariableSpecification_getNamedVariableRecursive(ctlVarSpec, "Oper"); MmsVariableSpecification* oper = MmsVariableSpecification_getNamedVariableRecursive(operSpec, "Oper");
if (oper) if (oper)
{ {
@ -194,8 +163,7 @@ ControlObjectClient_create(const char* objectReference, IedConnection connection
if ((ctlVal == NULL) || (t == NULL)) { if ((ctlVal == NULL) || (t == NULL)) {
if (DEBUG_IED_CLIENT) if (DEBUG_IED_CLIENT)
printf("IED_CLIENT: \"Oper\" is missing required element\n"); printf("IED_CLIENT: \"Oper\" is missing required element\n");
goto exit_function;
goto free_varspec;
} }
self = (ControlObjectClient) GLOBAL_CALLOC(1, sizeof(struct sControlObjectClient)); self = (ControlObjectClient) GLOBAL_CALLOC(1, sizeof(struct sControlObjectClient));
@ -230,8 +198,50 @@ ControlObjectClient_create(const char* objectReference, IedConnection connection
iedConnection_addControlClient(connection, self); iedConnection_addControlClient(connection, self);
free_varspec: exit_function:
MmsVariableSpecification_destroy(ctlVarSpec); 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: exit_function:
return self; return self;
@ -267,7 +277,7 @@ ControlObjectClient_setCommandTerminationHandler(ControlObjectClient self, Comma
self->commandTerminationHandler = handler; self->commandTerminationHandler = handler;
} }
char* const char*
ControlObjectClient_getObjectReference(ControlObjectClient self) ControlObjectClient_getObjectReference(ControlObjectClient self)
{ {
return self->objectReference; return self->objectReference;
@ -279,6 +289,19 @@ ControlObjectClient_getControlModel(ControlObjectClient self)
return self->ctlModel; 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 MmsType
ControlObjectClient_getCtlValType(ControlObjectClient self) ControlObjectClient_getCtlValType(ControlObjectClient self)
{ {
@ -346,18 +369,9 @@ createOriginValue(ControlObjectClient self)
return origin; return origin;
} }
bool static MmsValue*
ControlObjectClient_operate(ControlObjectClient self, MmsValue* ctlVal, uint64_t operTime) 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); resetLastApplError(self);
MmsValue* operParameters; MmsValue* operParameters;
@ -443,6 +457,37 @@ ControlObjectClient_operate(ControlObjectClient self, MmsValue* ctlVal, uint64_t
strncat(itemId, "$Oper", 64 - controlObjectItemIdLen); 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) if (DEBUG_IED_CLIENT)
printf("IED_CLIENT: operate: %s/%s\n", domainId, itemId); printf("IED_CLIENT: operate: %s/%s\n", domainId, itemId);
@ -470,14 +515,65 @@ ControlObjectClient_operate(ControlObjectClient self, MmsValue* ctlVal, uint64_t
success = true; success = true;
exit_function: exit_function:
return success; return success;
} }
bool static void
ControlObjectClient_selectWithValue(ControlObjectClient self, MmsValue* ctlVal) 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 domainId[65];
char itemId[65]; char itemId[65];
@ -486,13 +582,43 @@ ControlObjectClient_selectWithValue(ControlObjectClient self, MmsValue* ctlVal)
convertToMmsAndInsertFC(itemId, self->objectReference + strlen(domainId) + 1, "CO"); 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) 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; 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; int selValElementCount = 5;
if (self->hasTimeActivatedMode) if (self->hasTimeActivatedMode)
@ -553,7 +679,39 @@ ControlObjectClient_selectWithValue(ControlObjectClient self, MmsValue* ctlVal)
MmsValue_setBitStringBit(check, 0, self->synchroCheck); MmsValue_setBitStringBit(check, 0, self->synchroCheck);
MmsValue_setElement(selValParameters, index++, check); 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); &mmsError, domainId, itemId, selValParameters);
MmsValue_setElement(selValParameters, 0, NULL); MmsValue_setElement(selValParameters, 0, NULL);
@ -564,6 +722,13 @@ ControlObjectClient_selectWithValue(ControlObjectClient self, MmsValue* ctlVal)
printf("IED_CLIENT: select-with-value failed!\n"); printf("IED_CLIENT: select-with-value failed!\n");
return false; 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); MmsValue_update(self->ctlVal, ctlVal);
@ -573,6 +738,110 @@ ControlObjectClient_selectWithValue(ControlObjectClient self, MmsValue* ctlVal)
return true; 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 bool
ControlObjectClient_select(ControlObjectClient self) ControlObjectClient_select(ControlObjectClient self)
{ {
@ -631,15 +900,119 @@ ControlObjectClient_select(ControlObjectClient self)
MmsValue_delete(value); MmsValue_delete(value);
exit_function: exit_function:
return selected; return selected;
} }
bool static void
ControlObjectClient_cancel(ControlObjectClient self) 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); 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; MmsValue* cancelParameters;
if (self->hasTimeActivatedMode) if (self->hasTimeActivatedMode)
@ -683,6 +1056,16 @@ ControlObjectClient_cancel(ControlObjectClient self)
MmsValue* ctlTest = MmsValue_newBoolean(self->test); MmsValue* ctlTest = MmsValue_newBoolean(self->test);
MmsValue_setElement(cancelParameters, index++, ctlTest); MmsValue_setElement(cancelParameters, index++, ctlTest);
return cancelParameters;
}
bool
ControlObjectClient_cancel(ControlObjectClient self)
{
resetLastApplError(self);
MmsValue* cancelParameters = createCancelParameters(self);
char domainId[65]; char domainId[65];
char itemId[65]; char itemId[65];
@ -697,7 +1080,7 @@ ControlObjectClient_cancel(ControlObjectClient self)
MmsError mmsError; MmsError mmsError;
MmsConnection_writeVariable(IedConnection_getMmsConnection(self->connection), MmsDataAccessError writeResult = MmsConnection_writeVariable(IedConnection_getMmsConnection(self->connection),
&mmsError, domainId, itemId, cancelParameters); &mmsError, domainId, itemId, cancelParameters);
MmsValue_setElement(cancelParameters, 0, NULL); MmsValue_setElement(cancelParameters, 0, NULL);
@ -708,10 +1091,109 @@ ControlObjectClient_cancel(ControlObjectClient self)
printf("IED_CLIENT: cancel failed!\n"); printf("IED_CLIENT: cancel failed!\n");
return false; return false;
} }
else {
if (writeResult != DATA_ACCESS_ERROR_SUCCESS) {
if (DEBUG_IED_CLIENT)
printf("IED_CLIENT: cancel failed!\n");
return false;
}
}
return true; 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 void
ControlObjectClient_useConstantT(ControlObjectClient self, bool useConstantT) ControlObjectClient_useConstantT(ControlObjectClient self, bool useConstantT)
{ {

@ -352,7 +352,7 @@ iedConnection_doesControlObjectMatch(const char* objRef, const char* cntrlObj)
} }
static bool static bool
doesReportMatchControlObject(char* domainName, char* itemName, char* objectRef) doesReportMatchControlObject(char* domainName, char* itemName, const char* objectRef)
{ {
int i = 0; int i = 0;
@ -510,7 +510,7 @@ informationReportHandler(void* parameter, char* domainName,
while (control != NULL) { while (control != NULL) {
ControlObjectClient object = (ControlObjectClient) control->data; ControlObjectClient object = (ControlObjectClient) control->data;
char* objectRef = ControlObjectClient_getObjectReference(object); const char* objectRef = ControlObjectClient_getObjectReference(object);
if (doesReportMatchControlObject(domainName, variableListName, objectRef)) if (doesReportMatchControlObject(domainName, variableListName, objectRef))
controlObjectClient_invokeCommandTerminationHandler(object); controlObjectClient_invokeCommandTerminationHandler(object);

@ -1771,11 +1771,32 @@ ClientDataSet_getDataSetSize(ClientDataSet self);
typedef struct sControlObjectClient* ControlObjectClient; typedef struct sControlObjectClient* ControlObjectClient;
typedef enum { typedef enum {
CONTROL_MODEL_STATUS_ONLY, /**
CONTROL_MODEL_DIRECT_NORMAL, * No support for control functions. Control object only support status information.
CONTROL_MODEL_SBO_NORMAL, */
CONTROL_MODEL_DIRECT_ENHANCED, CONTROL_MODEL_STATUS_ONLY = 0,
CONTROL_MODEL_SBO_ENHANCED
/**
* 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; } ControlModel;
@ -1783,7 +1804,12 @@ typedef enum {
* \brief Create a new client control object * \brief Create a new client control object
* *
* A client control object is used to handle all client side aspects of a controllable * 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 objectReference the reference of the controllable data object
* \param connection the connection instance where the control object has to be reached * \param connection the connection instance where the control object has to be reached
@ -1793,15 +1819,68 @@ typedef enum {
LIB61850_API ControlObjectClient LIB61850_API ControlObjectClient
ControlObjectClient_create(const char* objectReference, IedConnection connection); 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 LIB61850_API void
ControlObjectClient_destroy(ControlObjectClient self); 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); 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 LIB61850_API ControlModel
ControlObjectClient_getControlModel(ControlObjectClient self); 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. * \brief Get the type of ctlVal.
* *
@ -1867,9 +1946,80 @@ ControlObjectClient_selectWithValue(ControlObjectClient self, MmsValue* ctlVal);
LIB61850_API bool LIB61850_API bool
ControlObjectClient_cancel(ControlObjectClient self); 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 LIB61850_API LastApplError
ControlObjectClient_getLastApplError(ControlObjectClient self); 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 * The origin parameter is used to identify the client/application that sent a control
* command. It is intended for later analysis. * 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 LIB61850_API void
ControlObjectClient_setOrigin(ControlObjectClient self, const char* orIdent, int orCat); ControlObjectClient_setOrigin(ControlObjectClient self, const char* orIdent, int orCat);
@ -1909,13 +2062,13 @@ ControlObjectClient_useConstantT(ControlObjectClient self, bool useConstantT);
/** /**
* \deprecated use ControlObjectClient_setInterlockCheck instead * \deprecated use ControlObjectClient_setInterlockCheck instead
*/ */
LIB61850_API void LIB61850_API DEPRECATED void
ControlObjectClient_enableInterlockCheck(ControlObjectClient self); ControlObjectClient_enableInterlockCheck(ControlObjectClient self);
/** /**
* \deprecated use ControlObjectClient_setSynchroCheck instead * \deprecated use ControlObjectClient_setSynchroCheck instead
*/ */
LIB61850_API void LIB61850_API DEPRECATED void
ControlObjectClient_enableSynchroCheck(ControlObjectClient self); ControlObjectClient_enableSynchroCheck(ControlObjectClient self);
/** /**
@ -1944,12 +2097,22 @@ ControlObjectClient_setSynchroCheck(ControlObjectClient self, bool value);
* To distinguish between a CommandTermination+ and CommandTermination- please use the * To distinguish between a CommandTermination+ and CommandTermination- please use the
* ControlObjectClient_getLastApplError function. * ControlObjectClient_getLastApplError function.
* *
* \param self the ControlObjectClient instance * \param parameter the user paramter that is passed to the callback function
* \param handler the callback function to be used * \param controlClient the ControlObjectClient instance
* \param handlerParameter an arbitrary parameter that is passed to the handler
*/ */
typedef void (*CommandTerminationHandler) (void* parameter, ControlObjectClient controlClient); 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 LIB61850_API void
ControlObjectClient_setCommandTerminationHandler(ControlObjectClient self, CommandTerminationHandler handler, ControlObjectClient_setCommandTerminationHandler(ControlObjectClient self, CommandTerminationHandler handler,
void* handlerParameter); void* handlerParameter);

@ -122,6 +122,9 @@ ClientReport_destroy(ClientReport self);
LIB61850_INTERNAL void LIB61850_INTERNAL void
controlObjectClient_invokeCommandTerminationHandler(ControlObjectClient self); controlObjectClient_invokeCommandTerminationHandler(ControlObjectClient self);
LIB61850_INTERNAL void
ControlObjectClient_setLastApplError(ControlObjectClient self, LastApplError lastAppIError);
/* some declarations that are shared with server side ! */ /* some declarations that are shared with server side ! */
LIB61850_INTERNAL char* LIB61850_INTERNAL char*

Loading…
Cancel
Save