Skip to content

Commit

Permalink
fix(server): allow method calling for alarm children objects (#75)
Browse files Browse the repository at this point in the history
  • Loading branch information
dpazj authored Dec 5, 2024
1 parent e94699c commit 41a24e4
Show file tree
Hide file tree
Showing 3 changed files with 190 additions and 62 deletions.
2 changes: 2 additions & 0 deletions src/server/ua_server_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,8 @@ getAllInterfaceChildNodeIds(UA_Server *server, const UA_NodeId *objectNode, cons
struct UA_ConditionBranch;
typedef struct UA_ConditionBranch UA_ConditionBranch;

UA_Boolean isCondition (UA_Server *server, const UA_NodeId *id);

UA_ConditionBranch *UA_getConditionBranch (UA_Server *server, const UA_NodeId *conditionBranchId);

UA_StatusCode
Expand Down
133 changes: 133 additions & 0 deletions src/server/ua_services_method.c
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,123 @@ iterateFunctionGroupSearch(void *context, UA_ReferenceTarget *t) {
return NULL;
}

#ifdef UA_ENABLE_SUBSCRIPTIONS_ALARMS_CONDITIONS

struct methodIsAlarmIterData
{
UA_Server *server;
const UA_NodeId *alarmId;
};

static void * methodIsAlarmMethodIterateObjectNodes
(void *context, UA_ReferenceTarget *t)
{
UA_Server *server = ((struct methodIsAlarmIterData*) context)->server;
const UA_NodeId *alarmId = ((struct methodIsAlarmIterData*) context)->alarmId;
if(!UA_NodePointer_isLocal(t->targetId)) return NULL;

UA_NodeId tmpId = UA_NodePointer_toNodeId(t->targetId);
if (UA_NodeId_equal(&tmpId, alarmId)) return (void *) 0x01;

const UA_Node *objectNode = UA_NODESTORE_GETFROMREF(server, t->targetId);
if (!objectNode) return NULL;
if (objectNode->head.nodeClass != UA_NODECLASS_OBJECT) goto done;

const UA_Node *objectNodeType = getNodeType (server, &objectNode->head);
if (!objectNodeType) goto done;

//is type a subtype of alarms
UA_NodeId alarmType = UA_NODEID_NUMERIC (0 , UA_NS0ID_ALARMCONDITIONTYPE);
UA_Boolean isSubtype = isNodeInTree_singleRef(server, &objectNodeType->head.nodeId, &alarmType,
UA_REFERENCETYPEINDEX_HASSUBTYPE);
UA_NODESTORE_RELEASE(server, objectNodeType);
if (isSubtype) goto done;


UA_ReferenceTypeSet hasComponentRefs;
UA_StatusCode res = referenceTypeIndices(server, &hasComponentNodeId,
&hasComponentRefs, true);
if (res != UA_STATUSCODE_GOOD) return NULL;

for (size_t i = 0; i < objectNode->head.referencesSize; ++i) {
UA_NodeReferenceKind *rk = &objectNode->head.references[i];
if(!rk->isInverse)
continue;

/* Are these ComponentOf references */
if(!UA_ReferenceTypeSet_contains(&hasComponentRefs, rk->referenceTypeIndex))
continue;

if (UA_NodeReferenceKind_iterate (rk, methodIsAlarmMethodIterateObjectNodes, context)) return (void *) 0x01;
}
done:
UA_NODESTORE_RELEASE(server, objectNode);
return NULL;
}

static void *
methodIsAlarmMethodIterateMethodComponentOfReferences (void *context, UA_ReferenceTarget *t)
{

UA_Server *server = ((struct methodIsAlarmIterData*) context)->server;
if(!UA_NodePointer_isLocal(t->targetId)) return NULL;

const UA_Node *parentNode = UA_NODESTORE_GETFROMREF(server, t->targetId);
if (!parentNode) return NULL;

const UA_Node *parentType = getNodeType(server, &parentNode->head);
UA_NODESTORE_RELEASE(server, parentNode);
if (!parentType) return NULL;

UA_ReferenceTypeSet hasTypeDefinitionRefs;
UA_StatusCode res = referenceTypeIndices(server, &hasTypeDefinitionNodeId,
&hasTypeDefinitionRefs, true);
if (res != UA_STATUSCODE_GOOD) return NULL;

for (size_t i = 0; i < parentType->head.referencesSize; ++i) {
UA_NodeReferenceKind *rk = &parentType->head.references[i];
if(!rk->isInverse)
continue;

/* Are these TypeDefinitionOf references */
if(!UA_ReferenceTypeSet_contains(&hasTypeDefinitionRefs, rk->referenceTypeIndex))
continue;

if (UA_NodeReferenceKind_iterate (rk, methodIsAlarmMethodIterateObjectNodes, context)) return (void *) 0x01;
}
UA_NODESTORE_RELEASE(server, parentType);
return NULL;
}

static UA_Boolean checkMethodIsAlarmMethod (
UA_Server *server,
const UA_NodeId *alarmId,
const UA_MethodNode *method,
UA_ReferenceTypeSet *hasComponentRefs
)
{
if (!isCondition (server, alarmId)) return false;

struct methodIsAlarmIterData data = {.server = server, .alarmId = alarmId};

UA_Boolean methodIsAlarmMethod = false;

for (size_t i = 0; i < method->head.referencesSize; ++i) {
UA_NodeReferenceKind *rk = &method->head.references[i];
if(!rk->isInverse)
continue;

/* Are these Component of references */
if(!UA_ReferenceTypeSet_contains(hasComponentRefs, rk->referenceTypeIndex))
continue;

methodIsAlarmMethod = UA_NodeReferenceKind_iterate (rk, methodIsAlarmMethodIterateMethodComponentOfReferences, &data);
if (methodIsAlarmMethod) break;
}
return methodIsAlarmMethod;
}
#endif

static UA_StatusCode
checkFunctionalGroupMethodReference(UA_Server *server, const UA_NodeHead *h,
const UA_ExpandedNodeId *methodId,
Expand Down Expand Up @@ -245,6 +362,22 @@ callWithMethodAndObject(UA_Server *server, UA_Session *session,
}
}

#ifdef UA_ENABLE_SUBSCRIPTIONS_ALARMS_CONDITIONS
if (!found) {
/* https://reference.opcfoundation.org/Core/Part9/v105/docs/5.8.17
* Servers shall allow Clients to call the ShelvedStateMachine Methods
* of its Shelving child by specifying ConditionId as the ObjectId
* where the ConditionId is the Condition that has Shelving child.
*
* We can extend this behaviour to check if the objectId is a condition
* and that the methodId is a component of an object that the condition
*
*
* */
found = checkMethodIsAlarmMethod (server, &object->head.nodeId, method, &hasComponentRefs);
}
#endif

if(!found) {
/* The following ParentObject evaluation is a workaround only to fulfill
* the OPC UA Spec. Part 100 - Devices requirements regarding functional
Expand Down
117 changes: 55 additions & 62 deletions src/server/ua_subscription_alarms_conditions.c
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,11 @@ inline UA_ConditionBranch *UA_getConditionBranch (UA_Server *server, const UA_No
return getConditionBranch(server, conditionBranchId);
}

UA_Boolean isCondition (UA_Server *server, const UA_NodeId *id)
{
return getCondition(server, id) ? true : false;
}

UA_StatusCode
UA_getConditionId(UA_Server *server, const UA_NodeId *conditionNodeId,
UA_NodeId *outConditionId)
Expand Down Expand Up @@ -1863,26 +1868,25 @@ condition_timedShelve (UA_Server *server, UA_Condition *condition,
}

static UA_StatusCode
condition_oneShotShelveStart (UA_Server *server, UA_Condition *condition)
condition_oneShotShelve (UA_Server *server, UA_Condition *condition, const UA_LocalizedText *comment, const UA_ConditionEventInfo *eventInfo)
{
UA_Condition_removeUnshelveCallback(condition, server);
if (UA_Condition_State_isOneShotShelved(condition, server)) return UA_STATUSCODE_BADCONDITIONALREADYSHELVED;
UA_Duration maxTimeShelved = UA_DOUBLE_MAX;
UA_Condition_State_getMaxTimeShelved(condition, server, &maxTimeShelved);
UA_StatusCode status = UA_Condition_State_setShelvingStateOneShot(
UA_Boolean timedUnshelve = false;
if (UA_Condition_State_getMaxTimeShelved(condition, server, &maxTimeShelved) == UA_STATUSCODE_GOOD)
{
timedUnshelve = true;
}
UA_StatusCode status = UA_Condition_State_setShelvingStateOneShot (
condition,
server,
maxTimeShelved
);

if (status != UA_STATUSCODE_GOOD) return status;
status = createUnshelveTimedCallback(server, condition, maxTimeShelved);
return UA_STATUSCODE_GOOD;
}
UA_Condition_removeUnshelveCallback(condition, server);
if (timedUnshelve) status = createUnshelveTimedCallback(server, condition, maxTimeShelved);

static UA_StatusCode
condition_oneShotShelve (UA_Server *server, UA_Condition *condition, const UA_LocalizedText *comment, const UA_ConditionEventInfo *eventInfo)
{
if (UA_Condition_State_isOneShotShelved(condition, server)) return UA_STATUSCODE_BADCONDITIONALREADYSHELVED;
UA_StatusCode status = condition_oneShotShelveStart(server, condition);
if (status != UA_STATUSCODE_GOOD) return status;
UA_ConditionEventInfo info = {
.message = UA_LOCALIZEDTEXT(LOCALE, ONESHOTSHELVE_MESSAGE)
Expand Down Expand Up @@ -3246,6 +3250,7 @@ oneShotShelveMethodCallback(UA_Server *server, const UA_NodeId *sessionId,
const UA_Variant *input, size_t outputSize,
UA_Variant *output)
{
// assert (false);
return UA_Server_Condition_oneShotShelve (server, *objectId, NULL, NULL);
}

Expand Down Expand Up @@ -3941,57 +3946,45 @@ void initNs0ConditionAndAlarms (UA_Server *server)
* references methods without copying them when creating objects. So the
* callbacks will be attached to the methods of the conditionType. */

UA_NodeId methodId[] = {
{0, UA_NODEIDTYPE_NUMERIC, {UA_NS0ID_CONDITIONTYPE_DISABLE}},
{0, UA_NODEIDTYPE_NUMERIC, {UA_NS0ID_CONDITIONTYPE_ENABLE}},
{0, UA_NODEIDTYPE_NUMERIC, {UA_NS0ID_CONDITIONTYPE_ADDCOMMENT}},
{0, UA_NODEIDTYPE_NUMERIC, {UA_NS0ID_CONDITIONTYPE_CONDITIONREFRESH}},
{0, UA_NODEIDTYPE_NUMERIC, {UA_NS0ID_CONDITIONTYPE_CONDITIONREFRESH2}},
{0, UA_NODEIDTYPE_NUMERIC, {UA_NS0ID_ACKNOWLEDGEABLECONDITIONTYPE_ACKNOWLEDGE}},
{0, UA_NODEIDTYPE_NUMERIC, {UA_NS0ID_ACKNOWLEDGEABLECONDITIONTYPE_CONFIRM}},
{0, UA_NODEIDTYPE_NUMERIC, {UA_NS0ID_ALARMCONDITIONTYPE_RESET}},
{0, UA_NODEIDTYPE_NUMERIC, {UA_NS0ID_ALARMCONDITIONTYPE_RESET2}},
{0, UA_NODEIDTYPE_NUMERIC, {UA_NS0ID_ALARMCONDITIONTYPE_SUPPRESS}},
{0, UA_NODEIDTYPE_NUMERIC, {UA_NS0ID_ALARMCONDITIONTYPE_SUPPRESS2}},
{0, UA_NODEIDTYPE_NUMERIC, {UA_NS0ID_ALARMCONDITIONTYPE_UNSUPPRESS}},
{0, UA_NODEIDTYPE_NUMERIC, {UA_NS0ID_ALARMCONDITIONTYPE_UNSUPPRESS}},
{0, UA_NODEIDTYPE_NUMERIC, {UA_NS0ID_ALARMCONDITIONTYPE_PLACEINSERVICE}},
{0, UA_NODEIDTYPE_NUMERIC, {UA_NS0ID_ALARMCONDITIONTYPE_PLACEINSERVICE2}},
{0, UA_NODEIDTYPE_NUMERIC, {UA_NS0ID_ALARMCONDITIONTYPE_REMOVEFROMSERVICE}},
{0, UA_NODEIDTYPE_NUMERIC, {UA_NS0ID_ALARMCONDITIONTYPE_REMOVEFROMSERVICE2}},

{0, UA_NODEIDTYPE_NUMERIC, {UA_NS0ID_SHELVEDSTATEMACHINETYPE_TIMEDSHELVE}},
{0, UA_NODEIDTYPE_NUMERIC, {UA_NS0ID_SHELVEDSTATEMACHINETYPE_ONESHOTSHELVE}},
{0, UA_NODEIDTYPE_NUMERIC, {UA_NS0ID_SHELVEDSTATEMACHINETYPE_UNSHELVE}},
{0, UA_NODEIDTYPE_NUMERIC, {UA_NS0ID_SHELVEDSTATEMACHINETYPE_TIMEDSHELVE2}},
{0, UA_NODEIDTYPE_NUMERIC, {UA_NS0ID_SHELVEDSTATEMACHINETYPE_ONESHOTSHELVE2}},
{0, UA_NODEIDTYPE_NUMERIC, {UA_NS0ID_SHELVEDSTATEMACHINETYPE_UNSHELVE2}}
struct _UA_Set_MethodNode_MethodCallback_data {
UA_NodeId id;
UA_MethodCallback cb;
};

retval |= setMethodNode_callback(server, methodId[0], disableMethodCallback);
retval |= setMethodNode_callback(server, methodId[1], enableMethodCallback);
retval |= setMethodNode_callback(server, methodId[2], addCommentMethodCallback);
retval |= setMethodNode_callback(server, methodId[3], refreshMethodCallback);
retval |= setMethodNode_callback(server, methodId[4], refresh2MethodCallback);
retval |= setMethodNode_callback(server, methodId[5], acknowledgeMethodCallback);
retval |= setMethodNode_callback(server, methodId[6], confirmMethodCallback);
retval |= setMethodNode_callback(server, methodId[7], resetMethodCallback);
retval |= setMethodNode_callback(server, methodId[8], reset2MethodCallback);
retval |= setMethodNode_callback(server, methodId[9], suppressMethodCallback);
retval |= setMethodNode_callback(server, methodId[10], suppress2MethodCallback);
retval |= setMethodNode_callback(server, methodId[11], unsuppressMethodCallback);
retval |= setMethodNode_callback(server, methodId[12], unsuppress2MethodCallback);
retval |= setMethodNode_callback(server, methodId[13], placeInServiceMethodCallback);
retval |= setMethodNode_callback(server, methodId[14], placeInService2MethodCallback);
retval |= setMethodNode_callback(server, methodId[15], removeFromServiceMethodCallback);
retval |= setMethodNode_callback(server, methodId[16], removeFromService2MethodCallback);

retval |= setMethodNode_callback(server, methodId[17], timedShelveMethodCallback);
retval |= setMethodNode_callback(server, methodId[18], oneShotShelveMethodCallback);
retval |= setMethodNode_callback(server, methodId[19], unshelveMethodCallback);
retval |= setMethodNode_callback(server, methodId[20], timedShelve2MethodCallback);
retval |= setMethodNode_callback(server, methodId[21], oneShotShelve2MethodCallback);
retval |= setMethodNode_callback(server, methodId[22], unshelve2MethodCallback);
struct _UA_Set_MethodNode_MethodCallback_data condition_methods[] = {
{.id = UA_NODEID_NUMERIC(0, UA_NS0ID_CONDITIONTYPE_CONDITIONREFRESH), .cb = refreshMethodCallback},
{.id = UA_NODEID_NUMERIC(0, UA_NS0ID_CONDITIONTYPE_CONDITIONREFRESH2), .cb = refresh2MethodCallback},
{.id = UA_NODEID_NUMERIC(0, UA_NS0ID_CONDITIONTYPE_DISABLE), .cb = disableMethodCallback},
{.id = UA_NODEID_NUMERIC(0, UA_NS0ID_CONDITIONTYPE_ENABLE), .cb = enableMethodCallback},
{.id = UA_NODEID_NUMERIC(0, UA_NS0ID_CONDITIONTYPE_ADDCOMMENT), .cb = addCommentMethodCallback},

{.id = UA_NODEID_NUMERIC(0, UA_NS0ID_ACKNOWLEDGEABLECONDITIONTYPE_ACKNOWLEDGE), .cb = acknowledgeMethodCallback},
{.id = UA_NODEID_NUMERIC(0, UA_NS0ID_ACKNOWLEDGEABLECONDITIONTYPE_CONFIRM), .cb = confirmMethodCallback},

{.id = UA_NODEID_NUMERIC(0, UA_NS0ID_ALARMCONDITIONTYPE_RESET), .cb = resetMethodCallback},
{.id = UA_NODEID_NUMERIC(0, UA_NS0ID_ALARMCONDITIONTYPE_RESET2), .cb = reset2MethodCallback},
{.id = UA_NODEID_NUMERIC(0, UA_NS0ID_ALARMCONDITIONTYPE_SUPPRESS), .cb = suppressMethodCallback},
{.id = UA_NODEID_NUMERIC(0, UA_NS0ID_ALARMCONDITIONTYPE_SUPPRESS2), .cb = suppress2MethodCallback},
{.id = UA_NODEID_NUMERIC(0, UA_NS0ID_ALARMCONDITIONTYPE_UNSUPPRESS), .cb = unsuppressMethodCallback},
{.id = UA_NODEID_NUMERIC(0, UA_NS0ID_ALARMCONDITIONTYPE_UNSUPPRESS2), .cb = unsuppress2MethodCallback},
{.id = UA_NODEID_NUMERIC(0, UA_NS0ID_ALARMCONDITIONTYPE_PLACEINSERVICE), .cb = placeInServiceMethodCallback},
{.id = UA_NODEID_NUMERIC(0, UA_NS0ID_ALARMCONDITIONTYPE_PLACEINSERVICE2), .cb = placeInService2MethodCallback},
{.id = UA_NODEID_NUMERIC(0, UA_NS0ID_ALARMCONDITIONTYPE_REMOVEFROMSERVICE), .cb = removeFromServiceMethodCallback},
{.id = UA_NODEID_NUMERIC(0, UA_NS0ID_ALARMCONDITIONTYPE_REMOVEFROMSERVICE2), .cb = removeFromService2MethodCallback},

{.id = UA_NODEID_NUMERIC(0, UA_NS0ID_ALARMCONDITIONTYPE_SHELVINGSTATE_UNSHELVE), .cb = unshelveMethodCallback},
{.id = UA_NODEID_NUMERIC(0, UA_NS0ID_ALARMCONDITIONTYPE_SHELVINGSTATE_UNSHELVE2), .cb = unshelve2MethodCallback},
{.id = UA_NODEID_NUMERIC(0, UA_NS0ID_ALARMCONDITIONTYPE_SHELVINGSTATE_TIMEDSHELVE), .cb = timedShelveMethodCallback},
{.id = UA_NODEID_NUMERIC(0, UA_NS0ID_ALARMCONDITIONTYPE_SHELVINGSTATE_TIMEDSHELVE2), .cb = timedShelve2MethodCallback},
{.id = UA_NODEID_NUMERIC(0, UA_NS0ID_ALARMCONDITIONTYPE_SHELVINGSTATE_ONESHOTSHELVE), .cb = oneShotShelveMethodCallback},
{.id = UA_NODEID_NUMERIC(0, UA_NS0ID_ALARMCONDITIONTYPE_SHELVINGSTATE_ONESHOTSHELVE2), .cb = oneShotShelve2MethodCallback},
};

for (size_t i=0; i< sizeof(condition_methods)/sizeof(condition_methods[0]); i++)
{
retval |= setMethodNode_callback(server, condition_methods[i].id, condition_methods[i].cb);
}


// Create RefreshEvents
if(UA_NodeId_isNull(&server->refreshEvents[REFRESHEVENT_START_IDX]) &&
Expand Down Expand Up @@ -4102,7 +4095,7 @@ static UA_StatusCode calculateNewLimitState (
if (exclusive) goto done;
}
}

readObjectPropertyDouble (server, *conditionId, UA_QUALIFIEDNAME(0, CONDITION_FIELD_LOWLOWDEADBAND), &lowLowDeadband);
retval = readObjectPropertyDouble (server, *conditionId, UA_QUALIFIEDNAME(0, CONDITION_FIELD_LOWLOWLIMIT), &lowLowLimit);
if (retval == UA_STATUSCODE_GOOD)
Expand Down

0 comments on commit 41a24e4

Please sign in to comment.