From 41a24e49285b7c041ec095dd004a6d316dce8e3c Mon Sep 17 00:00:00 2001 From: Joe Date: Thu, 5 Dec 2024 13:15:37 +0000 Subject: [PATCH] fix(server): allow method calling for alarm children objects (#75) --- src/server/ua_server_internal.h | 2 + src/server/ua_services_method.c | 133 ++++++++++++++++++ .../ua_subscription_alarms_conditions.c | 117 ++++++++------- 3 files changed, 190 insertions(+), 62 deletions(-) diff --git a/src/server/ua_server_internal.h b/src/server/ua_server_internal.h index 43f984d49e8..5e3ecb79bdd 100644 --- a/src/server/ua_server_internal.h +++ b/src/server/ua_server_internal.h @@ -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 diff --git a/src/server/ua_services_method.c b/src/server/ua_services_method.c index d8f8e8ecfe0..88fbba67d54 100644 --- a/src/server/ua_services_method.c +++ b/src/server/ua_services_method.c @@ -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, @@ -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 diff --git a/src/server/ua_subscription_alarms_conditions.c b/src/server/ua_subscription_alarms_conditions.c index 3f5fab2e6ad..5c4de1f50d6 100644 --- a/src/server/ua_subscription_alarms_conditions.c +++ b/src/server/ua_subscription_alarms_conditions.c @@ -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) @@ -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) @@ -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); } @@ -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]) && @@ -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)