/* This work is licensed under a Creative Commons CCZero 1.0 Universal License. * See http://creativecommons.org/publicdomain/zero/1.0/ for more information. */ #include #include #include #include #include /** * Using Alarms and Conditions Server * ---------------------------------- * * Besides the usage of monitored items and events to observe the changes in the * server, it is also important to make use of the Alarms and Conditions Server * Model. Alarms are events which are triggered automatically by the server * dependent on internal server logic or user specific logic when the states of * server components change. The state of a component is represented through a * condition. So the values of all the condition children (Fields) are the * actual state of the component. * * Trigger Alarm events by changing States * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ * * The following example will be based on the server events tutorial. Please * make sure to understand the principle of normal events before proceeding with * this example! */ static UA_NodeId conditionSource; static UA_NodeId conditionInstance_1; static UA_NodeId conditionInstance_2; static UA_StatusCode addConditionSourceObject(UA_Server *server) { UA_ObjectAttributes object_attr = UA_ObjectAttributes_default; object_attr.eventNotifier = 1; object_attr.displayName = UA_LOCALIZEDTEXT("en", "ConditionSourceObject"); UA_StatusCode retval = UA_Server_addObjectNode(server, UA_NODEID_NULL, UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER), UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES), UA_QUALIFIEDNAME(0, "ConditionSourceObject"), UA_NODEID_NUMERIC(0, UA_NS0ID_BASEOBJECTTYPE), object_attr, NULL, &conditionSource); if(retval != UA_STATUSCODE_GOOD) { UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Creating Condition Source failed. StatusCode %s", UA_StatusCode_name(retval)); } /* ConditionSource should be EventNotifier of another Object (usually the * Server Object). If this Reference is not created by user then the A&C * Server will create "HasEventSource" reference to the Server Object * automatically when the condition is created*/ retval = UA_Server_addReference(server, UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER), UA_NODEID_NUMERIC(0, UA_NS0ID_HASNOTIFIER), UA_EXPANDEDNODEID_NUMERIC(conditionSource.namespaceIndex, conditionSource.identifier.numeric), UA_TRUE); return retval; } /** * Create a condition instance from OffNormalAlarmType. The condition source is * the Object created in addConditionSourceObject(). The condition will be * exposed in Address Space through the HasComponent reference to the condition * source. */ static UA_StatusCode addCondition_1(UA_Server *server) { UA_StatusCode retval = addConditionSourceObject(server); if(retval != UA_STATUSCODE_GOOD) { UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "creating Condition Source failed. StatusCode %s", UA_StatusCode_name(retval)); } retval = UA_Server_createCondition(server, UA_NODEID_NULL, UA_NODEID_NUMERIC(0, UA_NS0ID_OFFNORMALALARMTYPE), UA_QUALIFIEDNAME(0, "Condition 1"), conditionSource, UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), &conditionInstance_1); return retval; } /** * Create a condition instance from OffNormalAlarmType. The condition source is * the server Object. The condition won't be exposed in Address Space. */ static UA_StatusCode addCondition_2(UA_Server *server) { UA_StatusCode retval = UA_Server_createCondition(server, UA_NODEID_NULL, UA_NODEID_NUMERIC(0, UA_NS0ID_OFFNORMALALARMTYPE), UA_QUALIFIEDNAME(0, "Condition 2"), UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER), UA_NODEID_NULL, &conditionInstance_2); return retval; } static void addVariable_1_triggerAlarmOfCondition_1(UA_Server *server, UA_NodeId* outNodeId) { UA_VariableAttributes attr = UA_VariableAttributes_default; attr.displayName = UA_LOCALIZEDTEXT("en", "Activate Condition 1"); attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE; UA_Boolean tboolValue = UA_FALSE; UA_Variant_setScalar(&attr.value, &tboolValue, &UA_TYPES[UA_TYPES_BOOLEAN]); UA_QualifiedName CallbackTestVariableName = UA_QUALIFIEDNAME(0, "Activate Condition 1"); UA_NodeId parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER); UA_NodeId parentReferenceNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES); UA_NodeId variableTypeNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE); UA_Server_addVariableNode(server, UA_NODEID_NULL, parentNodeId, parentReferenceNodeId, CallbackTestVariableName, variableTypeNodeId, attr, NULL, outNodeId); } static void addVariable_2_changeSeverityOfCondition_2(UA_Server *server, UA_NodeId* outNodeId) { UA_VariableAttributes attr = UA_VariableAttributes_default; attr.displayName = UA_LOCALIZEDTEXT("en", "Change Severity Condition 2"); attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE; UA_UInt16 severityValue = 0; UA_Variant_setScalar(&attr.value, &severityValue, &UA_TYPES[UA_TYPES_UINT16]); UA_QualifiedName CallbackTestVariableName = UA_QUALIFIEDNAME(0, "Change Severity Condition 2"); UA_NodeId parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER); UA_NodeId parentReferenceNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES); UA_NodeId variableTypeNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE); UA_Server_addVariableNode(server, UA_NODEID_NULL, parentNodeId, parentReferenceNodeId, CallbackTestVariableName, variableTypeNodeId, attr, NULL, outNodeId); } static void addVariable_3_returnCondition_1_toNormalState(UA_Server *server, UA_NodeId* outNodeId) { UA_VariableAttributes attr = UA_VariableAttributes_default; attr.displayName = UA_LOCALIZEDTEXT("en", "Return to Normal Condition 1"); attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE; UA_Boolean rtn = 0; UA_Variant_setScalar(&attr.value, &rtn, &UA_TYPES[UA_TYPES_BOOLEAN]); UA_QualifiedName CallbackTestVariableName = UA_QUALIFIEDNAME(0, "Return to Normal Condition 1"); UA_NodeId parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER); UA_NodeId parentReferenceNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES); UA_NodeId variableTypeNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE); UA_Server_addVariableNode(server, UA_NODEID_NULL, parentNodeId, parentReferenceNodeId, CallbackTestVariableName, variableTypeNodeId, attr, NULL, outNodeId); } static void afterWriteCallbackVariable_1(UA_Server *server, const UA_NodeId *sessionId, void *sessionContext, const UA_NodeId *nodeId, void *nodeContext, const UA_NumericRange *range, const UA_DataValue *data) { UA_QualifiedName activeStateField = UA_QUALIFIEDNAME(0,"ActiveState"); UA_QualifiedName activeStateIdField = UA_QUALIFIEDNAME(0,"Id"); UA_Variant value; UA_StatusCode retval = UA_Server_writeObjectProperty_scalar(server, conditionInstance_1, UA_QUALIFIEDNAME(0, "Time"), &data->sourceTimestamp, &UA_TYPES[UA_TYPES_DATETIME]); if(*(UA_Boolean *)(data->value.data) == true) { /* By writing "true" in ActiveState/Id, the A&C server will set the * related fields automatically and then will trigger event * notification. */ UA_Boolean activeStateId = true; UA_Variant_setScalar(&value, &activeStateId, &UA_TYPES[UA_TYPES_BOOLEAN]); retval |= UA_Server_setConditionVariableFieldProperty(server, conditionInstance_1, &value, activeStateField, activeStateIdField); if(retval != UA_STATUSCODE_GOOD) { UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Setting ActiveState/Id Field failed. StatusCode %s", UA_StatusCode_name(retval)); return; } } else { /* By writing "false" in ActiveState/Id, the A&C server will set only * the ActiveState field automatically to the value "Inactive". The user * should trigger the event manually by calling * UA_Server_triggerConditionEvent inside the application or call * ConditionRefresh method with client to update the event notification. */ UA_Boolean activeStateId = false; UA_Variant_setScalar(&value, &activeStateId, &UA_TYPES[UA_TYPES_BOOLEAN]); retval = UA_Server_setConditionVariableFieldProperty(server, conditionInstance_1, &value, activeStateField, activeStateIdField); if(retval != UA_STATUSCODE_GOOD) { UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Setting ActiveState/Id Field failed. StatusCode %s", UA_StatusCode_name(retval)); return; } retval = UA_Server_triggerConditionEvent(server, conditionInstance_1, conditionSource, NULL); if(retval != UA_STATUSCODE_GOOD) { UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Triggering condition event failed. StatusCode %s", UA_StatusCode_name(retval)); return; } } } /** * The callback only changes the severity field of the condition 2. The severity * field is of ConditionVariableType, so changes in it triggers an event * notification automatically by the server. */ static void afterWriteCallbackVariable_2(UA_Server *server, const UA_NodeId *sessionId, void *sessionContext, const UA_NodeId *nodeId, void *nodeContext, const UA_NumericRange *range, const UA_DataValue *data) { /* Another way to set fields of conditions */ UA_Server_writeObjectProperty_scalar(server, conditionInstance_2, UA_QUALIFIEDNAME(0, "Severity"), (UA_UInt16 *)data->value.data, &UA_TYPES[UA_TYPES_UINT16]); } /** * RTN = return to normal. * * Retain will be set to false, thus no events will be generated for condition 1 * (although EnabledState/=true). To set Retain to true again, the disable and * enable methods should be called respectively. */ static void afterWriteCallbackVariable_3(UA_Server *server, const UA_NodeId *sessionId, void *sessionContext, const UA_NodeId *nodeId, void *nodeContext, const UA_NumericRange *range, const UA_DataValue *data) { //UA_QualifiedName enabledStateField = UA_QUALIFIEDNAME(0,"EnabledState"); UA_QualifiedName ackedStateField = UA_QUALIFIEDNAME(0,"AckedState"); UA_QualifiedName confirmedStateField = UA_QUALIFIEDNAME(0,"ConfirmedState"); UA_QualifiedName activeStateField = UA_QUALIFIEDNAME(0,"ActiveState"); UA_QualifiedName severityField = UA_QUALIFIEDNAME(0,"Severity"); UA_QualifiedName messageField = UA_QUALIFIEDNAME(0,"Message"); UA_QualifiedName commentField = UA_QUALIFIEDNAME(0,"Comment"); UA_QualifiedName retainField = UA_QUALIFIEDNAME(0,"Retain"); UA_QualifiedName idField = UA_QUALIFIEDNAME(0,"Id"); UA_StatusCode retval = UA_Server_writeObjectProperty_scalar(server, conditionInstance_1, UA_QUALIFIEDNAME(0, "Time"), &data->serverTimestamp, &UA_TYPES[UA_TYPES_DATETIME]); UA_Variant value; UA_Boolean idValue = false; UA_Variant_setScalar(&value, &idValue, &UA_TYPES[UA_TYPES_BOOLEAN]); retval |= UA_Server_setConditionVariableFieldProperty(server, conditionInstance_1, &value, activeStateField, idField); if(retval != UA_STATUSCODE_GOOD) { UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Setting ActiveState/Id Field failed. StatusCode %s", UA_StatusCode_name(retval)); return; } retval = UA_Server_setConditionVariableFieldProperty(server, conditionInstance_1, &value, ackedStateField, idField); if(retval != UA_STATUSCODE_GOOD) { UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Setting AckedState/Id Field failed. StatusCode %s", UA_StatusCode_name(retval)); return; } retval = UA_Server_setConditionVariableFieldProperty(server, conditionInstance_1, &value, confirmedStateField, idField); if(retval != UA_STATUSCODE_GOOD) { UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Setting ConfirmedState/Id Field failed. StatusCode %s", UA_StatusCode_name(retval)); return; } UA_UInt16 severityValue = 100; UA_Variant_setScalar(&value, &severityValue, &UA_TYPES[UA_TYPES_UINT16]); retval = UA_Server_setConditionField(server, conditionInstance_1, &value, severityField); if(retval != UA_STATUSCODE_GOOD) { UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Setting Severity Field failed. StatusCode %s", UA_StatusCode_name(retval)); return; } UA_LocalizedText messageValue = UA_LOCALIZEDTEXT("en", "Condition returned to normal state"); UA_Variant_setScalar(&value, &messageValue, &UA_TYPES[UA_TYPES_LOCALIZEDTEXT]); retval = UA_Server_setConditionField(server, conditionInstance_1, &value, messageField); if(retval != UA_STATUSCODE_GOOD) { UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Setting Message Field failed. StatusCode %s", UA_StatusCode_name(retval)); return; } UA_LocalizedText commentValue = UA_LOCALIZEDTEXT("en", "Normal State"); UA_Variant_setScalar(&value, &commentValue, &UA_TYPES[UA_TYPES_LOCALIZEDTEXT]); retval = UA_Server_setConditionField(server, conditionInstance_1, &value, commentField); if(retval != UA_STATUSCODE_GOOD) { UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Setting Comment Field failed. StatusCode %s", UA_StatusCode_name(retval)); return; } UA_Boolean retainValue = false; UA_Variant_setScalar(&value, &retainValue, &UA_TYPES[UA_TYPES_BOOLEAN]); retval = UA_Server_setConditionField(server, conditionInstance_1, &value, retainField); if(retval != UA_STATUSCODE_GOOD) { UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Setting Retain Field failed. StatusCode %s", UA_StatusCode_name(retval)); return; } retval = UA_Server_triggerConditionEvent(server, conditionInstance_1, conditionSource, NULL); if (retval != UA_STATUSCODE_GOOD) { UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Triggering condition event failed. StatusCode %s", UA_StatusCode_name(retval)); return; } } static UA_StatusCode enteringEnabledStateCallback(UA_Server *server, const UA_NodeId *condition) { UA_Boolean retain = true; return UA_Server_writeObjectProperty_scalar(server, *condition, UA_QUALIFIEDNAME(0, "Retain"), &retain, &UA_TYPES[UA_TYPES_BOOLEAN]); } /** * This is user specific function which will be called upon acknowledging an * alarm notification. In this example we will set the Alarm to Inactive state. * The server is responsible of setting standard fields related to Acknowledge * Method and triggering the alarm notification. */ static UA_StatusCode enteringAckedStateCallback(UA_Server *server, const UA_NodeId *condition) { /* deactivate Alarm when acknowledging*/ UA_Boolean activeStateId = false; UA_Variant value; UA_QualifiedName activeStateField = UA_QUALIFIEDNAME(0,"ActiveState"); UA_QualifiedName activeStateIdField = UA_QUALIFIEDNAME(0,"Id"); UA_Variant_setScalar(&value, &activeStateId, &UA_TYPES[UA_TYPES_BOOLEAN]); UA_StatusCode retval = UA_Server_setConditionVariableFieldProperty(server, *condition, &value, activeStateField, activeStateIdField); if(retval != UA_STATUSCODE_GOOD) { UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Setting ActiveState/Id Field failed. StatusCode %s", UA_StatusCode_name(retval)); } return retval; } static UA_StatusCode enteringConfirmedStateCallback(UA_Server *server, const UA_NodeId *condition) { /* Deactivate Alarm and put it out of the interesting state (by writing * false to Retain field) when confirming*/ UA_Boolean activeStateId = false; UA_Boolean retain = false; UA_Variant value; UA_QualifiedName activeStateField = UA_QUALIFIEDNAME(0,"ActiveState"); UA_QualifiedName activeStateIdField = UA_QUALIFIEDNAME(0,"Id"); UA_QualifiedName retainField = UA_QUALIFIEDNAME(0,"Retain"); UA_Variant_setScalar(&value, &activeStateId, &UA_TYPES[UA_TYPES_BOOLEAN]); UA_StatusCode retval = UA_Server_setConditionVariableFieldProperty(server, *condition, &value, activeStateField, activeStateIdField); if(retval != UA_STATUSCODE_GOOD) { UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Setting ActiveState/Id Field failed. StatusCode %s", UA_StatusCode_name(retval)); return retval; } UA_Variant_setScalar(&value, &retain, &UA_TYPES[UA_TYPES_BOOLEAN]); retval = UA_Server_setConditionField(server, *condition, &value, retainField); if(retval != UA_STATUSCODE_GOOD) { UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Setting ActiveState/Id Field failed. StatusCode %s", UA_StatusCode_name(retval)); } return retval; } static UA_StatusCode setUpEnvironment(UA_Server *server) { UA_NodeId variable_1; UA_NodeId variable_2; UA_NodeId variable_3; UA_ValueCallback callback; callback.onRead = NULL; /* Exposed condition 1. We will add to it user specific callbacks when * entering enabled state, when acknowledging and when confirming. */ UA_StatusCode retval = addCondition_1(server); if(retval != UA_STATUSCODE_GOOD) { UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "adding condition 1 failed. StatusCode %s", UA_StatusCode_name(retval)); return retval; } UA_TwoStateVariableChangeCallback userSpecificCallback = enteringEnabledStateCallback; retval = UA_Server_setConditionTwoStateVariableCallback(server, conditionInstance_1, conditionSource, false, userSpecificCallback, UA_ENTERING_ENABLEDSTATE); if(retval != UA_STATUSCODE_GOOD) { UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "adding entering enabled state callback failed. StatusCode %s", UA_StatusCode_name(retval)); return retval; } userSpecificCallback = enteringAckedStateCallback; retval = UA_Server_setConditionTwoStateVariableCallback(server, conditionInstance_1, conditionSource, false, userSpecificCallback, UA_ENTERING_ACKEDSTATE); if(retval != UA_STATUSCODE_GOOD) { UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "adding entering acked state callback failed. StatusCode %s", UA_StatusCode_name(retval)); return retval; } userSpecificCallback = enteringConfirmedStateCallback; retval = UA_Server_setConditionTwoStateVariableCallback(server, conditionInstance_1, conditionSource, false, userSpecificCallback, UA_ENTERING_CONFIRMEDSTATE); if(retval != UA_STATUSCODE_GOOD) { UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "adding entering confirmed state callback failed. StatusCode %s", UA_StatusCode_name(retval)); return retval; } /* Unexposed condition 2. No user specific callbacks, so the server will * behave in a standard manner upon entering enabled state, acknowledging * and confirming. We will set Retain field to true and enable the condition * so we can receive event notifications (we cannot call enable method on * unexposed condition using a client like UaExpert or Softing). */ retval = addCondition_2(server); if(retval != UA_STATUSCODE_GOOD) { UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "adding condition 2 failed. StatusCode %s", UA_StatusCode_name(retval)); return retval; } UA_Boolean retain = UA_TRUE; UA_Server_writeObjectProperty_scalar(server, conditionInstance_2, UA_QUALIFIEDNAME(0, "Retain"), &retain, &UA_TYPES[UA_TYPES_BOOLEAN]); UA_Variant value; UA_Boolean enabledStateId = true; UA_QualifiedName enabledStateField = UA_QUALIFIEDNAME(0,"EnabledState"); UA_QualifiedName enabledStateIdField = UA_QUALIFIEDNAME(0,"Id"); UA_Variant_setScalar(&value, &enabledStateId, &UA_TYPES[UA_TYPES_BOOLEAN]); retval = UA_Server_setConditionVariableFieldProperty(server, conditionInstance_2, &value, enabledStateField, enabledStateIdField); if(retval != UA_STATUSCODE_GOOD) { UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Setting EnabledState/Id Field failed. StatusCode %s", UA_StatusCode_name(retval)); return retval; } /* Add 3 variables to trigger condition events */ addVariable_1_triggerAlarmOfCondition_1(server, &variable_1); callback.onWrite = afterWriteCallbackVariable_1; retval = UA_Server_setVariableNode_valueCallback(server, variable_1, callback); if(retval != UA_STATUSCODE_GOOD) { UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Setting variable 1 Callback failed. StatusCode %s", UA_StatusCode_name(retval)); return retval; } /* Severity can change internally also when the condition disabled and * retain is false. However, in this case no events will be generated. */ addVariable_2_changeSeverityOfCondition_2(server, &variable_2); callback.onWrite = afterWriteCallbackVariable_2; retval = UA_Server_setVariableNode_valueCallback(server, variable_2, callback); if(retval != UA_STATUSCODE_GOOD) { UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Setting variable 2 Callback failed. StatusCode %s", UA_StatusCode_name(retval)); return retval; } addVariable_3_returnCondition_1_toNormalState(server, &variable_3); callback.onWrite = afterWriteCallbackVariable_3; retval = UA_Server_setVariableNode_valueCallback(server, variable_3, callback); if(retval != UA_STATUSCODE_GOOD) { UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Setting variable 3 Callback failed. StatusCode %s", UA_StatusCode_name(retval)); } return retval; } /** * It follows the main server code, making use of the above definitions. */ static UA_Boolean running = true; static void stopHandler(int sig) { running = false; } int main (void) { /* default server values */ signal(SIGINT, stopHandler); signal(SIGTERM, stopHandler); UA_Server *server = UA_Server_new(); UA_ServerConfig_setDefault(UA_Server_getConfig(server)); UA_StatusCode retval = setUpEnvironment(server); if(retval == UA_STATUSCODE_GOOD) retval = UA_Server_run(server, &running); UA_Server_delete(server); return retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE; }