OPC Studio User's Guide and Reference
OPC UA A&C Notification Event
View with Navigation Tools
Client and Subscriber Development > Development Models > Imperative Programming Model > Imperative Programming Model for OPC UA Alarms & Conditions > Subscribing to Information (OPC UA Alarms & Conditions) > OPC UA A&C Notification Event

When an OPC UA Alarms & Conditions server generates an event, the EasyUAClient object generates an  EventNotification event. For subscription mechanism to be useful, you should hook one or more event handlers to this event. This event is raised for every event generated by a subscribed OPC monitored item.

To be more precise, the EventNotification event is actually generated in other cases, too - if there is any significant occurrence related to the event subscription. This can be for three reasons:

  1. You receive the EventNotification when a successful connection (or re-connection) is made. In this case, the Exception and EventData properties of the event arguments are null references.
  2. You receive the EventNotification when there is a problem with the event subscription, and it is disconnected. In this case, the Exception property contains information about the error. The EventData property is a null reference.
  3. You receive one additional EventNotification when the component is about to send you notifications for condition refresh, and one additional EventNotification after the component has sent you all notifications for the condition refresh. In these cases, the RefreshInitiated or RefreshComplete property of the event arguments is set to 'true', and the Exception and EventData properties contain null references.

The notification for the EventNotification event contains an EasyUAEventNotificationEventArgs argument. You will find all kind of relevant data in this object. Some properties in this object contain valid information under all circumstances - e.g. Arguments (including Arguments.State). Other properties, such as EventData, contain null references when there is no associated information for them. When the EventData property is not a null reference, it contains a UAEventData object describing the detail of the actual OPC event received from the OPC UA Alarms & Conditions server.

Before further processing, your code should always inspect the value of Exception property of the event arguments. If this property is not a null reference, there has been an error related to the event subscription, the Exception property contains information about the problem, and the EventData property does not contain a valid object.

If the Exception property is a null reference, the notification may be informing you about the fact that a condition refresh is being initiated or is complete (in this case, the RefreshInitiated or RefreshComplete property is 'true'), or that an event subscription has been successfully connected or re-connected (in this case, the EventData property is a null reference). If none of the previous applies, the EventData property contains a valid UAEventData object with details about the actual OPC event generated by the OPC server.

Pseudo-code for the full EventNotification event handler may look similar to this:

if notificationEventArgs.Exception is not null then

        An error occurred and the subscription is disconnected, handle it (or ignore)

else if notificationEventArgs.RefreshInitiated then

        A “refresh” has been initiated; handle it

else if notificationEventArgs.RefreshComplete then

        A “refresh” is complete; handle it

else if notificationEventArgs.EventData is null then

        Subscription has been successfully connected or re-connected, handle it (or ignore)

else

        Handle the OPC event, details are in notificationEventArgs.EventData. You may use notificationEventArgs.Refresh flag for distinguishing refreshes from original notifications.

The EventNotification event handler is called on a thread determined by the EasyUAClient component. For details, please refer to “Multithreading and Synchronization” chapter under “Advanced Topics”.

The arguments of each event notification (EasyUAEventNotificationEventArgs) have an EventData property (of type UAEventData, described further below), which contains details about the OPC UA event. In addition, common information such as the copy of the input arguments to the subscription, i.e. identification of the endpoint and node (monitored item) which has generated the event, is included. The Exception property (and several related properties) indicates success (when it is a null reference), or a failure (in which case it contains an exception object describing the error).

The UAEventData object contains details about an OPC UA event, i.e. the information that the OPC UA server sends with the event notification. Primarily, it has a FieldResults property, which is a dictionary containing results for each of the fields in the select clauses of the event filter. Each field results is a ValueResult object, i.e. it can either contain the actual value, or an error indication.

.NET

// This example shows how to display all fields of incoming events, or extract specific fields.
//
// Find all latest examples here: https://opclabs.doc-that.com/files/onlinedocs/OPCLabs-OpcStudio/Latest/examples.html .

using System;
using System.Collections.Generic;
using OpcLabs.BaseLib.OperationModel;
using OpcLabs.EasyOpc.UA;
using OpcLabs.EasyOpc.UA.AddressSpace.Standard;
using OpcLabs.EasyOpc.UA.AlarmsAndConditions;
using OpcLabs.EasyOpc.UA.Filtering;
using OpcLabs.EasyOpc.UA.OperationModel;

namespace UADocExamples.AlarmsAndConditions
{
    class FieldResults
    {
        public static void Main1()
        {
            UAEndpointDescriptor endpointDescriptor =
                "opc.tcp://opcua.demo-this.com:62544/Quickstarts/AlarmConditionServer";

            // Instantiate the client object and hook events.
            var client = new EasyUAClient();
            client.EventNotification += client_EventNotification;

            Console.WriteLine("Subscribing...");
            client.SubscribeEvent(endpointDescriptor, UAObjectIds.Server, 1000);

            Console.WriteLine("Processing event notifications for 30 seconds...");
            System.Threading.Thread.Sleep(30 * 1000);

            Console.WriteLine("Unsubscribing...");
            client.UnsubscribeAllMonitoredItems();

            Console.WriteLine("Waiting for 5 seconds...");
            System.Threading.Thread.Sleep(5 * 1000);

            Console.WriteLine("Finished.");
        }

        static void client_EventNotification(object sender, EasyUAEventNotificationEventArgs e)
        {
            Console.WriteLine();

            // Display the event.
            if (e.EventData is null)
            {
                Console.WriteLine(e);
                return;
            }
            Console.WriteLine("All fields:");
            foreach (KeyValuePair<UAAttributeField, ValueResult> pair in e.EventData.FieldResults)
            {
                UAAttributeField attributeField = pair.Key;
                ValueResult valueResult = pair.Value;
                Console.WriteLine("  {0} -> {1}", attributeField, valueResult);
            }

            // Extracting a specific field using a standard operand symbol.
            Console.WriteLine($"Source name: {e.EventData.FieldResults[UABaseEventObject.Operands.SourceName]}");

            // Extracting a specific field using an event type ID and a simple relative path.
            Console.WriteLine(
                $"Message: {e.EventData.FieldResults[UAFilterElements.SimpleAttribute(UAObjectTypeIds.BaseEventType, "/Message")]}");
        }


        // Example output (truncated):
        //Subscribing...
        //Processing event notifications for 30 seconds...
        //
        //[] Success
        //
        //[] Success; Refresh; RefreshInitiated
        //
        //All fields:
        //  NodeId="BaseEventType", NodeId -> Success; nsu=http://opcfoundation.org/Quickstarts/AlarmCondition ;ns=2;s=1:Colours/EastTank?OnlineState {OpcLabs.EasyOpc.UA.AddressSpace.UANodeId}
        //  NodeId="BaseEventType"/EventId -> Success; [16] {95, 68, 22, 205, 114, ...} {System.Byte[]}
        //  NodeId="BaseEventType"/EventType -> Success; DialogConditionType {OpcLabs.EasyOpc.UA.AddressSpace.UANodeId}
        //  NodeId="BaseEventType"/SourceNode -> Success; nsu=http://opcfoundation.org/Quickstarts/AlarmCondition ;ns=2;s=1:Colours/EastTank {OpcLabs.EasyOpc.UA.AddressSpace.UANodeId}
        //  NodeId="BaseEventType"/SourceName -> Success; EastTank {System.String}
        //  NodeId="BaseEventType"/Time -> Success; 9/10/2019 8:08:23 PM {System.DateTime}
        //  NodeId="BaseEventType"/ReceiveTime -> Success; 9/10/2019 8:08:23 PM {System.DateTime}
        //  NodeId="BaseEventType"/LocalTime -> Success; 00:00, DST {OpcLabs.EasyOpc.UA.UATimeZoneData}
        //  NodeId="BaseEventType"/Message -> Success; The dialog was activated {System.String}
        //  NodeId="BaseEventType"/Severity -> Success; 100 {System.Int32}
        //Source name: Success; EastTank {System.String}
        //Message: Success; The dialog was activated {System.String}
        //
        //All fields:
        //  NodeId="BaseEventType", NodeId -> Success; nsu=http://opcfoundation.org/Quickstarts/AlarmCondition ;ns=2;s=1:Colours/EastTank?Red {OpcLabs.EasyOpc.UA.AddressSpace.UANodeId}
        //  NodeId="BaseEventType"/EventId -> Success; [16] {124, 156, 219, 54, 120, ...} {System.Byte[]}
        //  NodeId="BaseEventType"/EventType -> Success; ExclusiveDeviationAlarmType {OpcLabs.EasyOpc.UA.AddressSpace.UANodeId}
        //  NodeId="BaseEventType"/SourceNode -> Success; nsu=http://opcfoundation.org/Quickstarts/AlarmCondition ;ns=2;s=1:Colours/EastTank {OpcLabs.EasyOpc.UA.AddressSpace.UANodeId}
        //  NodeId="BaseEventType"/SourceName -> Success; EastTank {System.String}
        //  NodeId="BaseEventType"/Time -> Success; 10/14/2019 4:00:13 PM {System.DateTime}
        //  NodeId="BaseEventType"/ReceiveTime -> Success; 10/14/2019 4:00:13 PM {System.DateTime}
        //  NodeId="BaseEventType"/LocalTime -> Success; 00:00, DST {OpcLabs.EasyOpc.UA.UATimeZoneData}
        //  NodeId="BaseEventType"/Message -> Success; The alarm was acknoweledged. {System.String}
        //  NodeId="BaseEventType"/Severity -> Success; 500 {System.Int32}
        //Source name: Success; EastTank {System.String}
        //Message: Success; The alarm was acknoweledged. {System.String}
        //
        //...
    }
}

COM

// This example shows how to display all fields of incoming events, or extract specific fields.
//
// Find all latest examples here : https://opclabs.doc-that.com/files/onlinedocs/OPCLabs-OpcStudio/Latest/examples.html .

type
  THelperMethods15 = class
    class function ObjectTypeIds_BaseEventType: _UANodeId; static;
    class function UAFilterElements_SimpleAttribute(TypeId: _UANodeId; simpleRelativeBrowsePathString: string): _UASimpleAttributeOperand; static;
    class function UABaseEventObject_Operands_SourceName: _UASimpleAttributeOperand; static;
    class function UABaseEventObject_Operands_Message: _UASimpleAttributeOperand; static;
  end;

type
  TClientEventHandlers15 = class
    procedure Client_EventNotification(
      ASender: TObject;
      sender: OleVariant;
      const eventArgs: _EasyUAEventNotificationEventArgs);
  end;

procedure TClientEventHandlers15.Client_EventNotification(
  ASender: TObject;
  sender: OleVariant;
  const eventArgs: _EasyUAEventNotificationEventArgs);
var
  AttributeField: OleVariant;
  Count: Cardinal;
  Element: OleVariant;
  EntryEnumerator: IEnumVARIANT;
  ValueResult: OleVariant;
begin
  WriteLn;

  // Display the event
  if eventArgs.EventData = nil then
  begin
    WriteLn(eventArgs.ToString);
    Exit;
  end;

  WriteLn('All fields:');

  EntryEnumerator := eventArgs.EventData.FieldResults.GetEnumerator;
  while (EntryEnumerator.Next(1, Element, Count) = S_OK) do
  begin
    AttributeField := IUnknown(Element.Key) as _UAAttributeField;
    ValueResult := IUnknown(Element.Value) as _ValueResult;
    WriteLn('  ', AttributeField.ToString, ' -> ', ValueResult.ToString);
  end;

  // Extracting specific fields using an event type ID and a simple relative path
  WriteLn('Source name: ', eventArgs.EventData.FieldResults.Item[THelperMethods15.UABaseEventObject_Operands_SourceName.ToUAAttributeField].ToString);
  WriteLn('Message: ', eventArgs.EventData.FieldResults.Item[THelperMethods15.UABaseEventObject_Operands_Message.ToUAAttributeField].ToString);
end;

class procedure FieldResults.Main;
const
  UAObjectIds_Server = 'nsu=http://opcfoundation.org/UA/;i=2253';
var
  Client: TEasyUAClient;
  ClientEventHandlers: TClientEventHandlers15;
  EndpointDescriptor: string;
begin
  EndpointDescriptor := 'opc.tcp://opcua.demo-this.com:62544/Quickstarts/AlarmConditionServer';

  // Instantiate the client object and hook events
  Client := TEasyUAClient.Create(nil);
  ClientEventHandlers := TClientEventHandlers15.Create;
  Client.OnEventNotification := ClientEventHandlers.Client_EventNotification;

  WriteLn('Subscribing...');
  Client.SubscribeEvent(EndpointDescriptor, UAObjectIds_Server, 1000);

  WriteLn('Processing event notifications for 30 seconds...');
  PumpSleep(30*1000);

  WriteLn('Unsubscribing...');
  Client.UnsubscribeAllMonitoredItems;

  WriteLn('Waiting for 5 seconds...');
  Sleep(5*1000);

  WriteLn('Finished.');
  FreeAndNil(Client);
  FreeAndNil(ClientEventHandlers);
end;

// Example output (truncated):
//Subscribing...
//Processing event notifications for 30 seconds...
//
//[] Success
//
///[] Success; Refresh; RefreshInitiated
//
//All fields:
//  NodeId="BaseEventType", NodeId -> Success; nsu=http://opcfoundation.org/Quickstarts/AlarmCondition ;ns=2;s=1:Colours/EastTank?OnlineState {OpcLabs.EasyOpc.UA.AddressSpace.UANodeId}
//  NodeId="BaseEventType"/EventId -> Success; [16] {95, 68, 22, 205, 114, ...} {System.Byte[]}
//  NodeId="BaseEventType"/EventType -> Success; DialogConditionType {OpcLabs.EasyOpc.UA.AddressSpace.UANodeId}
//  NodeId="BaseEventType"/SourceNode -> Success; nsu=http://opcfoundation.org/Quickstarts/AlarmCondition ;ns=2;s=1:Colours/EastTank {OpcLabs.EasyOpc.UA.AddressSpace.UANodeId}
//  NodeId="BaseEventType"/SourceName -> Success; EastTank {System.String}
//  NodeId="BaseEventType"/Time -> Success; 9/10/2019 8:08:23 PM {System.DateTime}
//  NodeId="BaseEventType"/ReceiveTime -> Success; 9/10/2019 8:08:23 PM {System.DateTime}
//  NodeId="BaseEventType"/LocalTime -> Success; 00:00, DST {OpcLabs.EasyOpc.UA.UATimeZoneData}
//  NodeId="BaseEventType"/Message -> Success; The dialog was activated {System.String}
//  NodeId="BaseEventType"/Severity -> Success; 100 {System.Int32}
//Source name: Success; EastTank {System.String}
//Message: Success; The dialog was activated {System.String}
//
//All fields:
//  NodeId="BaseEventType", NodeId -> Success; nsu=http://opcfoundation.org/Quickstarts/AlarmCondition ;ns=2;s=1:Colours/EastTank?Red {OpcLabs.EasyOpc.UA.AddressSpace.UANodeId}
//  NodeId="BaseEventType"/EventId -> Success; [16] {124, 156, 219, 54, 120, ...} {System.Byte[]}
//  NodeId="BaseEventType"/EventType -> Success; ExclusiveDeviationAlarmType {OpcLabs.EasyOpc.UA.AddressSpace.UANodeId}
//  NodeId="BaseEventType"/SourceNode -> Success; nsu=http://opcfoundation.org/Quickstarts/AlarmCondition ;ns=2;s=1:Colours/EastTank {OpcLabs.EasyOpc.UA.AddressSpace.UANodeId}
//  NodeId="BaseEventType"/SourceName -> Success; EastTank {System.String}
//  NodeId="BaseEventType"/Time -> Success; 10/14/2019 4:00:13 PM {System.DateTime}
//  NodeId="BaseEventType"/ReceiveTime -> Success; 10/14/2019 4:00:13 PM {System.DateTime}
//  NodeId="BaseEventType"/LocalTime -> Success; 00:00, DST {OpcLabs.EasyOpc.UA.UATimeZoneData}
//  NodeId="BaseEventType"/Message -> Success; The alarm was acknoweledged. {System.String}
//  NodeId="BaseEventType"/Severity -> Success; 500 {System.Int32}
//Source name: Success; EastTank {System.String}
//Message: Success; The alarm was acknoweledged. {System.String}
//
//...

class function THelperMethods15.ObjectTypeIds_BaseEventType: _UANodeId;
  var NodeId: _UANodeId;
  begin
    NodeId := CoUANodeId.Create;
    NodeId.StandardName := 'BaseEventType';
    Result := NodeId;
  end;

class function THelperMethods15.UAFilterElements_SimpleAttribute(TypeId: _UANodeId; simpleRelativeBrowsePathString: string): _UASimpleAttributeOperand;
var
  BrowsePathParser: _UABrowsePathParser;
  Operand: _UASimpleAttributeOperand;
begin
  BrowsePathParser := CoUABrowsePathParser.Create;
  Operand := CoUASimpleAttributeOperand.Create;
  Operand.TypeId.NodeId := TypeId;
  Operand.QualifiedNames := BrowsePathParser.ParseRelative(simpleRelativeBrowsePathString).ToUAQualifiedNameCollection;
  Result := Operand;
end;

class function THelperMethods15.UABaseEventObject_Operands_SourceName: _UASimpleAttributeOperand;
begin
  Result := UAFilterElements_SimpleAttribute(ObjectTypeIds_BaseEventType, '/SourceName');
end;

class function THelperMethods15.UABaseEventObject_Operands_Message: _UASimpleAttributeOperand;
begin
  Result := UAFilterElements_SimpleAttribute(ObjectTypeIds_BaseEventType, '/Message');
end;

 

In order to make access to at least some of these results easier (without having to go through indexing an element in the dictionary), the UAEventData object also contains a BaseEvent property (of UABaseEventObject type). The UABaseEventObject class contains properties that directly correspond to information contained in the base event type (from which all UA events are derived), such as SourceNameTimeMessageSeverity, etc. In case of an error related to a particular field, the corresponding property will contain its default value (e.g. an empty string for a Message).

.NET

// This example shows how to display specific fields of incoming events that are derived from base event type.
//
// Find all latest examples here: https://opclabs.doc-that.com/files/onlinedocs/OPCLabs-OpcStudio/Latest/examples.html .

using System;
using OpcLabs.EasyOpc.UA;
using OpcLabs.EasyOpc.UA.AddressSpace.Standard;
using OpcLabs.EasyOpc.UA.AlarmsAndConditions;
using OpcLabs.EasyOpc.UA.OperationModel;

namespace UADocExamples.AlarmsAndConditions
{
    class BaseEvent
    {
        public static void Main1()
        {
            UAEndpointDescriptor endpointDescriptor =
                "opc.tcp://opcua.demo-this.com:62544/Quickstarts/AlarmConditionServer";

            // Instantiate the client object and hook events.
            var client = new EasyUAClient();
            client.EventNotification += client_EventNotification;

            Console.WriteLine("Subscribing...");
            client.SubscribeEvent(endpointDescriptor, UAObjectIds.Server, 1000);

            Console.WriteLine("Processing event notifications for 30 seconds...");
            System.Threading.Thread.Sleep(30 * 1000);

            Console.WriteLine("Unsubscribing...");
            client.UnsubscribeAllMonitoredItems();

            Console.WriteLine("Waiting for 5 seconds...");
            System.Threading.Thread.Sleep(5 * 1000);

            Console.WriteLine("Finished.");
        }

        static void client_EventNotification(object sender, EasyUAEventNotificationEventArgs e)
        {
            Console.WriteLine();

            // Display the event.
            if (e.EventData is null)
            {
                Console.WriteLine(e);
                return;
            }
            UABaseEventObject baseEventObject = e.EventData.BaseEvent;
            Console.WriteLine($"Source name: {baseEventObject.SourceName}");
            Console.WriteLine($"Message: {baseEventObject.Message}");
            Console.WriteLine($"Severity: {baseEventObject.Severity}");
        }
    }
}

COM

// This example shows how to display specific fields of incoming events that are derived from base event type.
//
// Find all latest examples here : https://opclabs.doc-that.com/files/onlinedocs/OPCLabs-OpcStudio/Latest/examples.html .

type
  TClientEventHandlers12 = class
    procedure Client_EventNotification(
      ASender: TObject;
      sender: OleVariant;
      const eventArgs: _EasyUAEventNotificationEventArgs);
  end;

procedure TClientEventHandlers12.Client_EventNotification(
  ASender: TObject;
  sender: OleVariant;
  const eventArgs: _EasyUAEventNotificationEventArgs);
var
  BaseEventObject: _UABaseEventObject;
begin
  WriteLn;

  // Display the event
  if eventArgs.EventData = nil then
  begin
    WriteLn(eventArgs.ToString);
    Exit;
  end;

  BaseEventObject := eventArgs.EventData.BaseEvent;
  WriteLn('Source name: ', BaseEventObject.SourceName);
  WriteLn('Message: ', BaseEventObject.Message);
  WriteLn('Severity: ', BaseEventObject.Severity);
end;

class procedure BaseEvent.Main;
const
  UAObjectIds_Server = 'nsu=http://opcfoundation.org/UA/;i=2253';
var
  Client: TEasyUAClient;
  ClientEventHandlers: TClientEventHandlers12;
  EndpointDescriptor: string;
begin
  EndpointDescriptor := 'opc.tcp://opcua.demo-this.com:62544/Quickstarts/AlarmConditionServer';

  // Instantiate the client object and hook events
  Client := TEasyUAClient.Create(nil);
  ClientEventHandlers := TClientEventHandlers12.Create;
  Client.OnEventNotification := ClientEventHandlers.Client_EventNotification;

  WriteLn('Subscribing...');
  Client.SubscribeEvent(EndpointDescriptor, UAObjectIds_Server, 1000);

  WriteLn('Processing event notifications for 30 seconds...');
  PumpSleep(30*1000);

  WriteLn('Unsubscribing...');
  Client.UnsubscribeAllMonitoredItems;

  WriteLn('Waiting for 5 seconds...');
  Sleep(5*1000);

  WriteLn('Finished.');
  FreeAndNil(Client);
  FreeAndNil(ClientEventHandlers);
end;

 

It should be noted that the FieldResults directory always contains results for the fields you have specified in Select clause of the event filter, but it can contain some additional fields as well. This happens when these fields are necessary for internal processing inside the component. Your code should be able to ignore the results that it has not requested.

Note: Some data returned use complex data types defined by OPC UA, which QuickOPC transforms to its own .NET types. For example, the UATimeZoneData class contains data returned in the UABaseEventObject.LocalTime property (defines the local time that may or may not take daylight saving time into account).

 

See Also