QuickOPC User's Guide and Reference
Subscribing to OPC Classic Items
Development Models > Imperative Programming Model > Imperative Programming Model for OPC Data (Classic and UA) > Subscribing to Information (OPC Data) > Subscribing to OPC Classic Items
In This Topic

Subscription is initiated by calling either SubscribeItem or  SubscribeMultipleItems method. For any change in the subscribed item’s value, your application will receive the ItemChanged event notification, described further below. Obviously, you first need to hook up event handler for that event, and in order to prevent event loss, you should do it before subscribing. Alternatively, you can pass a callback method into the SubscribeItem or SubscribeMultipleItems call.

Values of some items may be changing quite frequently, and receiving all changes that are generated is not desirable for performance reasons; there are also physical limitations to the event throughput in the system. Your application needs to specify the requested update rate, which effectively tells the OPC server that you do not need to receive event notifications any faster than that. For OPC items that support it, you can optionally specify a percent deadband; only changes that exceed the deadband will generate an event notification.

In QuickOPC.NET, the requested update rate, percent deadband, and data type are all contained in a DAGroupParameters object.

A single item

If you want to subscribe to a specific OPC item, call the SubscribeItem method. You can pass in individual arguments for machine name, server class, ItemID, data type, requested update rate, and an optional percent deadband. Usually, you also pass in a State argument of type Object (in QuickOPC.NET) or VARIANT (in QuickOPC-COM). When the item’s value changes, the State argument is then passed to the ItemChanged event handler in the  EasyDAItemChangedEventArgs.Arguments.State property.  The SubscribeItem method returns a subscription handle that you can later use to change the subscription parameters, or unsubscribe.

Example 1

// Hooking up events and receiving OPC item changes.

using System;
using System.Threading;
using OpcLabs.EasyOpc.DataAccess;
using OpcLabs.EasyOpc.DataAccess.OperationModel;

namespace DocExamples.DataAccess._EasyDAClient
{
    partial class SubscribeItem
    {
        public static void Main1()
        {
            // Instantiate the client object.
            using (var client = new EasyDAClient())
            {
                var eventHandler = new EasyDAItemChangedEventHandler(client_ItemChanged);
                client.ItemChanged += eventHandler;

                Console.WriteLine("Subscribing item...");
                client.SubscribeItem("", "OPCLabs.KitServer.2", "Demo.Ramp", 200);

                Console.WriteLine("Process item change notifications for 30 seconds...");
                Thread.Sleep(30 * 1000);

                Console.WriteLine("Unsubscribing all items...");
                client.UnsubscribeAllItems();

                client.ItemChanged -= eventHandler;
            }

            Console.WriteLine("Finished.");
        }

        static void client_ItemChanged(object sender, EasyDAItemChangedEventArgs e)
        {
            if (e.Succeeded)
                Console.WriteLine(e.Vtq);
            else
                Console.WriteLine($"*** Failure: {e.ErrorMessageBrief}");
        }
    }
}
# Hooking up events and receiving OPC item changes.

#requires -Version 5.1
using namespace OpcLabs.EasyOpc.DataAccess

# The path below assumes that the current directory is [ProductDir]/Examples-NET/PowerShell/Windows .
Add-Type -Path "../../../Assemblies/net47/OpcLabs.EasyOpcClassic.dll"

# Instantiate the client object.
$client = New-Object EasyDAClient

# Item changed event handler
Register-ObjectEvent -InputObject $client -EventName ItemChanged -Action { 
    if ($EventArgs.Succeeded) {
        Write-Host $EventArgs.Vtq
    }
    else {
        Write-Host "*** Failure: $($EventArgs.ErrorMessageBrief)"
    }
}

Write-Host "Subscribing item..."
[IEasyDAClientExtension]::SubscribeItem($client, "", "OPCLabs.KitServer.2", "Demo.Ramp", 200)

Write-Host "Processing item changes for 30 seconds..."
$stopwatch =  [System.Diagnostics.Stopwatch]::StartNew() 
while ($stopwatch.Elapsed.TotalSeconds -lt 30) {    
    Start-Sleep -Seconds 1
}

Write-Host "Unsubscribing items..."
$client.UnsubscribeAllItems()

Write-Host "Finished."
' Hooking up events and receiving OPC item changes.

Imports System.Threading
Imports OpcLabs.EasyOpc.DataAccess
Imports OpcLabs.EasyOpc.DataAccess.OperationModel

Namespace DocExamples.DataAccess._EasyDAClient
    Partial Friend Class SubscribeItem
        Shared Sub Main1()
            Using client = New EasyDAClient()
                Dim eventHandler = New EasyDAItemChangedEventHandler(AddressOf client_ItemChanged)
                AddHandler client.ItemChanged, eventHandler

                Console.WriteLine("Subscribing item...")
                client.SubscribeItem("", "OPCLabs.KitServer.2", "Demo.Ramp", 200)
                Thread.Sleep(30 * 1000)
                client.UnsubscribeAllItems()
                RemoveHandler client.ItemChanged, eventHandler
            End Using
        End Sub

        Private Shared Sub client_ItemChanged(sender As Object, e As EasyDAItemChangedEventArgs)
            If e.Succeeded Then
                Console.WriteLine(e.Vtq)
            Else
                Console.WriteLine("*** Failure: {0}", e.ErrorMessageBrief)
            End If
        End Sub
    End Class
End Namespace
// This example shows how to subscribe to changes of a single item and display the value of the item with each change.

type
  TSubscribeItem_ClientEventHandlers = class
    // Item changed event handler
    procedure OnItemChanged(
      ASender: TObject;
      sender: OleVariant;
      const eventArgs: _EasyDAItemChangedEventArgs);
  end;

procedure TSubscribeItem_ClientEventHandlers.OnItemChanged(
  ASender: TObject;
  sender: OleVariant;
  const eventArgs: _EasyDAItemChangedEventArgs);
begin
  if eventArgs.Succeeded then
    WriteLn(eventArgs.Vtq.ToString)
  else
    WriteLn(Format('*** Failure: %s', [eventArgs.ErrorMessageBrief]));
end;

class procedure SubscribeItem.Main;
var
  Client: TEasyDAClient;
  ClientEventHandlers: TSubscribeItem_ClientEventHandlers;
begin
  // Instantiate the client object and hook events
  Client := TEasyDAClient.Create(nil);
  ClientEventHandlers := TSubscribeItem_ClientEventHandlers.Create;
  Client.OnItemChanged := ClientEventHandlers.OnItemChanged;

  Client.SubscribeItem('', 'OPCLabs.KitServer.2', 'Simulation.Random', 1000);

  WriteLn('Processing item changed events for 1 minute...');
  PumpSleep(60*1000);

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

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

  WriteLn('Finished.');
  FreeAndNil(Client);
  FreeAndNil(ClientEventHandlers);
end;
// This example shows how to subscribe to changes of a single item and display the value of the item with each change.
//
// Some related documentation: http://php.net/manual/en/function.com-event-sink.php . Pay attention to the comment that says 
// "Be careful how you use this feature; if you are doing something similar to the example below, then it doesn't really make 
// sense to run it in a web server context.". What they are trying to say is that processing a web request should be 
// a short-lived code, which does not fit well with the idea of being subscribed to events and received them over longer time. 
// It is possible to write such code, but it is only useful when processing the request is allowed to take relatively long. Or, 
// when you are using PHP from command-line, or otherwise - not to serve a web page directly.
// 
// Subscribing to QuickOPC-COM events in the context of PHP Web application, while not imposing the limitations to the request 
// processing time, has to be "worked around", e.g. using the "event pull" mechanism.

class DEasyDAClientEvents {
    function ItemChanged($varSender, $varE)
    {
        if ($varE->Succeeded)
        {
        print $varE->Vtq->ToString();
            print "\n";
        }
        else
        {
            printf("*** Failure: %s\n", $varE->ErrorMessageBrief);
        }
    }
}

$Client = new COM("OpcLabs.EasyOpc.DataAccess.EasyDAClient");
$Events = new DEasyDAClientEvents();
com_event_sink($Client, $Events, "DEasyDAClientEvents");

$Client->SubscribeItem("", "OPCLabs.KitServer.2", "Simulation.Random", 1000);

print "Processing item changed events for 1 minute...\n";
$startTime = time(); do { com_message_pump(1000); } while (time() < $startTime + 60);
Rem This example shows how to subscribe to changes of a single item and display the value of the item with each change.

Option Explicit

Dim Client: Set Client = CreateObject("OpcLabs.EasyOpc.DataAccess.EasyDAClient")
WScript.ConnectObject Client, "Client_"

Client.SubscribeItem "", "OPCLabs.KitServer.2", "Simulation.Random", 1000

WScript.Echo "Processing item changed events for 1 minute..."
WScript.Sleep 60*1000



Sub Client_ItemChanged(Sender, e)
    If Not (e.Succeeded) Then
        WScript.Echo "*** Failure: " & e.ErrorMessageBrief
        Exit Sub
    End If

    WScript.Echo e.Vtq.ToString
End Sub

Example 2

// This example subscribes to changes of 2 items separately, and displays rich information available with each item changed
// event notification.

using System;
using System.Diagnostics;
using System.Threading;
using OpcLabs.EasyOpc.DataAccess;
using OpcLabs.EasyOpc.DataAccess.OperationModel;

namespace DocExamples.DataAccess._EasyDAItemChangedEventArgs
{
    class General
    {
        public static void Main1()
        {
            // Instantiate the client object.
            using (var client = new EasyDAClient())
            {
                var eventHandler = new EasyDAItemChangedEventHandler(client_ItemChanged);
                client.ItemChanged += eventHandler;

                Console.WriteLine("Subscribing items...");
                client.SubscribeItem("", "OPCLabs.KitServer.2", "Simulation.Random", 5 * 1000);
                client.SubscribeItem("", "OPCLabs.KitServer.2", "Trends.Ramp (1 min)", 5 * 1000);

                Console.WriteLine("Processing item changed events for 1 minute...");
                Thread.Sleep(60 * 1000);
            }
        }

        static void client_ItemChanged(object sender, EasyDAItemChangedEventArgs e)
        {
            Console.WriteLine();
            Console.WriteLine($"e.Arguments.State: {e.Arguments.State}");
            Console.WriteLine($"e.Arguments.ServerDescriptor.MachineName: {e.Arguments.ServerDescriptor.MachineName}");
            Console.WriteLine($"e.Arguments.ServerDescriptor.ServerClass: {e.Arguments.ServerDescriptor.ServerClass}");
            Console.WriteLine($"e.Arguments.ItemDescriptor.ItemId: {e.Arguments.ItemDescriptor.ItemId}");
            Console.WriteLine($"e.Arguments.ItemDescriptor.AccessPath: {e.Arguments.ItemDescriptor.AccessPath}");
            Console.WriteLine($"e.Arguments.ItemDescriptor.RequestedDataType: {e.Arguments.ItemDescriptor.RequestedDataType}");
            Console.WriteLine($"e.Arguments.GroupParameters.Locale: {e.Arguments.GroupParameters.Locale}");
            Console.WriteLine($"e.Arguments.GroupParameters.RequestedUpdateRate: {e.Arguments.GroupParameters.RequestedUpdateRate}");
            Console.WriteLine($"e.Arguments.GroupParameters.PercentDeadband: {e.Arguments.GroupParameters.PercentDeadband}");
            if (e.Succeeded)
            {
                Debug.Assert(!(e.Vtq is null));
                Console.WriteLine($"e.Vtq.Value: {e.Vtq.Value}");
                Console.WriteLine($"e.Vtq.Timestamp: {e.Vtq.Timestamp}");
                Console.WriteLine($"e.Vtq.TimestampLocal: {e.Vtq.TimestampLocal}");
                Console.WriteLine($"e.Vtq.Quality: {e.Vtq.Quality}");
            }
            else
            {
                Debug.Assert(!(e.Exception is null));
                Console.WriteLine($"e.Exception.Message: {e.Exception.Message}");
                Console.WriteLine($"e.Exception.Source: {e.Exception.Source}");
            }
        }


        // Example output:
        //
        //Processing item changed events for 1 minute...
        //
        //e.Arguments.State: 
        //e.Arguments.ServerDescriptor.MachineName: 
        //e.Arguments.ServerDescriptor.ServerClass: OPCLabs.KitServer.2
        //e.Arguments.ItemDescriptor.ItemId: Simulation.Random
        //e.Arguments.ItemDescriptor.AccessPath: 
        //e.Arguments.ItemDescriptor.RequestedDataType: Empty
        //e.Arguments.GroupParameters.Locale: 0
        //e.Arguments.GroupParameters.RequestedUpdateRate: 5000
        //e.Arguments.GroupParameters.PercentDeadband: 0
        //e.Vtq.Value: 0.00125125888851588
        //e.Vtq.Timestamp: 4/10/2020 4:35:25 PM
        //e.Vtq.TimestampLocal: 4/10/2020 6:35:25 PM
        //e.Vtq.Quality: GoodNonspecific (192)
        //
        //e.Arguments.State: 
        //e.Arguments.ServerDescriptor.MachineName: 
        //e.Arguments.ServerDescriptor.ServerClass: OPCLabs.KitServer.2
        //e.Arguments.ItemDescriptor.ItemId: Trends.Ramp(1 min)
        //e.Arguments.ItemDescriptor.AccessPath: 
        //e.Arguments.ItemDescriptor.RequestedDataType: Empty
        //e.Arguments.GroupParameters.Locale: 0
        //e.Arguments.GroupParameters.RequestedUpdateRate: 5000
        //e.Arguments.GroupParameters.PercentDeadband: 0
        //e.Vtq.Value: 0.431881904602051
        //e.Vtq.Timestamp: 4/10/2020 4:35:25 PM
        //e.Vtq.TimestampLocal: 4/10/2020 6:35:25 PM
        //e.Vtq.Quality: GoodNonspecific (192)
        //
        //...
    }
}
Rem This example subscribes to changes of 2 items separately, and displays rich information available with each item changed
Rem event notification.

Option Explicit

Dim Client: Set Client = CreateObject("OpcLabs.EasyOpc.DataAccess.EasyDAClient")
WScript.ConnectObject Client, "Client_"

Client.SubscribeItem "", "OPCLabs.KitServer.2", "Simulation.Random", 5*1000
Client.SubscribeItem "", "OPCLabs.KitServer.2", "Trends.Ramp (1 min)", 5*1000

WScript.Echo "Processing item changed events for 1 minute..."
WScript.Sleep 60*1000



Sub Client_ItemChanged(Sender, e)
    On Error Resume Next
    WScript.Echo
    WScript.Echo "e.Arguments.State: " & e.Arguments.State
    WScript.Echo "e.Arguments.ServerDescriptor.MachineName: " & e.Arguments.ServerDescriptor.MachineName
    WScript.Echo "e.Arguments.ServerDescriptor.ServerClass: " & e.Arguments.ServerDescriptor.ServerClass
    WScript.Echo "e.Arguments.ItemDescriptor.ItemId: " & e.Arguments.ItemDescriptor.ItemId
    WScript.Echo "e.Arguments.ItemDescriptor.AccessPath: " & e.Arguments.ItemDescriptor.AccessPath
    WScript.Echo "e.Arguments.ItemDescriptor.RequestedDataType: " & e.Arguments.ItemDescriptor.RequestedDataType
    WScript.Echo "e.Arguments.GroupParameters.Locale: " & e.Arguments.GroupParameters.Locale
    WScript.Echo "e.Arguments.GroupParameters.RequestedUpdateRate: " & e.Arguments.GroupParameters.RequestedUpdateRate
    WScript.Echo "e.Arguments.GroupParameters.PercentDeadband: " & e.Arguments.GroupParameters.PercentDeadband
    WScript.Echo "e.Exception.Message: " & e.Exception.Message
    WScript.Echo "e.Exception.Source: " & e.Exception.Source
    WScript.Echo "e.Exception.ErrorCode: " & e.Exception.ErrorCode
    WScript.Echo "e.Vtq.Value: " & e.Vtq.Value
    WScript.Echo "e.Vtq.Timestamp: " & e.Vtq.Timestamp
    WScript.Echo "e.Vtq.TimestampLocal: " & e.Vtq.TimestampLocal
    WScript.Echo "e.Vtq.Quality: " & e.Vtq.Quality
End Sub

In QuickOPC.NET, you can also pass in a combination of ServerDescriptor, DAItemDescriptor and DAGroupParameters objects, in place of individual arguments.

The State argument is typically used to provide some sort of correlation between objects in your application, and the event notifications. For example, if you are programming an HMI application and you want the event handler to update the control that displays the item’s value, you may want to set the State argument to the control object itself. When the event notification arrives, you simply update the control indicated by the State property of EasyDAItemChangedEventArgs, without having to look it up by ItemId or so. See Use the state instead of handles to identify subscribed entities.

Multiple items

To subscribe to multiple items simultaneously in an efficient manner, call the SubscribeMultipleItems method (instead of multiple SubscribeItem calls in a loop). You receive back an array of integers, which are the subscription handles.

You pass in an array of  DAItemGroupArguments objects (each containing information for a single subscription to be made), to the SubscribeMultipleItems method.

// This example shows how subscribe to changes of multiple items and display the value of the item with each change.

using System;
using System.Threading;
using OpcLabs.EasyOpc.DataAccess;
using OpcLabs.EasyOpc.DataAccess.OperationModel;

namespace DocExamples.DataAccess._EasyDAClient
{
    partial class SubscribeMultipleItems
    {
        public static void Main1()
        {
            // Instantiate the client object.
            using (var client = new EasyDAClient())
            {
                client.ItemChanged += client_Main1_ItemChanged;

                Console.WriteLine("Subscribing item changes...");
                client.SubscribeMultipleItems(
                    new[] {
                            new DAItemGroupArguments("", "OPCLabs.KitServer.2", "Simulation.Random", 1000, null), 
                            new DAItemGroupArguments("", "OPCLabs.KitServer.2", "Trends.Ramp (1 min)", 1000, null), 
                            new DAItemGroupArguments("", "OPCLabs.KitServer.2", "Trends.Sine (1 min)", 1000, null),  
                            new DAItemGroupArguments("", "OPCLabs.KitServer.2", "Simulation.Register_I4", 1000, null)
                        });

                Console.WriteLine("Processing item changed events for 1 minute...");
                Thread.Sleep(60 * 1000);

                Console.WriteLine("Unsubscribing item changes...");
            }

            Console.WriteLine("Finished.");
        }

        // Item changed event handler
        static void client_Main1_ItemChanged(object sender, EasyDAItemChangedEventArgs e)
        {
            if (e.Succeeded)
                Console.WriteLine($"{e.Arguments.ItemDescriptor.ItemId}: {e.Vtq}");
            else
                Console.WriteLine($"{e.Arguments.ItemDescriptor.ItemId} *** Failure: {e.ErrorMessageBrief}");
        }
    }
}
# This example shows how subscribe to changes of multiple items and display the value of the item with each change.

#requires -Version 5.1
using namespace OpcLabs.EasyOpc.DataAccess
using namespace OpcLabs.EasyOpc.DataAccess.OperationModel

# The path below assumes that the current directory is [ProductDir]/Examples-NET/PowerShell/Windows .
Add-Type -Path "../../../Assemblies/net47/OpcLabs.EasyOpcClassic.dll"

# Instantiate the client object.
$client = New-Object EasyDAClient

# Item changed event handler
Register-ObjectEvent -InputObject $client -EventName ItemChanged -Action { 
    if ($EventArgs.Succeeded) {
        Write-Host "$($EventArgs.Arguments.ItemDescriptor.ItemId): $($EventArgs.Vtq)"
    }
    else {
        Write-Host "$($EventArgs.Arguments.ItemDescriptor.ItemId) *** Failure: $($EventArgs.ErrorMessageBrief)"
    }
}

Write-Host "Subscribing item changes..."
$handleArray = [IEasyDAClientExtension]::SubscribeMultipleItems($client, @(
    (New-Object DAItemGroupArguments("", "OPCLabs.KitServer.2", "Simulation.Random", 1000, $null)),
    (New-Object DAItemGroupArguments("", "OPCLabs.KitServer.2", "Trends.Ramp (1 min)", 1000, $null)),
    (New-Object DAItemGroupArguments("", "OPCLabs.KitServer.2", "Trends.Sine (1 min)", 1000, $null)),
    (New-Object DAItemGroupArguments("", "OPCLabs.KitServer.2", "Simulation.Register_I4", 1000, $null))
    ))

Write-Host "Processing item changed events for 1 minute..."
$stopwatch =  [System.Diagnostics.Stopwatch]::StartNew() 
while ($stopwatch.Elapsed.TotalSeconds -lt 30) {    
    Start-Sleep -Seconds 1
}

Write-Host "Unsubscribing item changes..."
$client.UnsubscribeAllItems()

Write-Host "Finished."
' This example shows how subscribe to changes of multiple items and display the value of the item with each change.

Imports System.Threading
Imports OpcLabs.EasyOpc.DataAccess
Imports OpcLabs.EasyOpc.DataAccess.OperationModel

Namespace DocExamples.DataAccess._EasyDAClient
    Partial Friend Class SubscribeMultipleItems
        Public Shared Sub Main1()
            Using client = New EasyDAClient()
                AddHandler client.ItemChanged, AddressOf client_ItemChanged_Main1

                client.SubscribeMultipleItems(New DAItemGroupArguments() { _
                 New DAItemGroupArguments("", "OPCLabs.KitServer.2", "Simulation.Random", 1000, Nothing), _
                 New DAItemGroupArguments("", "OPCLabs.KitServer.2", "Trends.Ramp (1 min)", 1000, Nothing), _
                 New DAItemGroupArguments("", "OPCLabs.KitServer.2", "Trends.Sine (1 min)", 1000, Nothing), _
                 New DAItemGroupArguments("", "OPCLabs.KitServer.2", "Simulation.Register_I4", 1000, Nothing) _
                })

                Console.WriteLine("Processing item changed events for 1 minute...")
                Thread.Sleep(60 * 1000)
            End Using
        End Sub

        ' Item changed event handler
        Private Shared Sub client_ItemChanged_Main1(ByVal sender As Object, ByVal e As EasyDAItemChangedEventArgs)
            ' Display the data
            If e.Succeeded Then
                Console.WriteLine("{0}: {1}", e.Arguments.ItemDescriptor.ItemId, e.Vtq)
            Else
                Console.WriteLine("{0} *** Failure: {1}", e.Arguments.ItemDescriptor.ItemId, e.ErrorMessageBrief)
            End If
        End Sub
    End Class
End Namespace
// This example shows how to subscribe to changes of multiple items and display the value of the item with each change.

class DEasyDAClientEvents {
    function ItemChanged($varSender, $varE)
    {
        if ($varE->Succeeded)
        {
            printf("s: s\n", $varE->Arguments->ItemDescriptor->ItemId, $varE->Vtq->ToString());
        }
        else
        {
            printf("*** Failure: %s\n", $varE->ErrorMessageBrief);
        }
    }
}

$ItemSubscriptionArguments1 = new COM("OpcLabs.EasyOpc.DataAccess.OperationModel.EasyDAItemSubscriptionArguments");
$ItemSubscriptionArguments1->ServerDescriptor->ServerClass = "OPCLabs.KitServer.2";
$ItemSubscriptionArguments1->ItemDescriptor->ItemID = "Simulation.Random";
$ItemSubscriptionArguments1->GroupParameters->RequestedUpdateRate = 1000;

$ItemSubscriptionArguments2 = new COM("OpcLabs.EasyOpc.DataAccess.OperationModel.EasyDAItemSubscriptionArguments");
$ItemSubscriptionArguments2->ServerDescriptor->ServerClass = "OPCLabs.KitServer.2";
$ItemSubscriptionArguments2->ItemDescriptor->ItemID = "Trends.Ramp (1 min)";
$ItemSubscriptionArguments2->GroupParameters->RequestedUpdateRate = 1000;

$ItemSubscriptionArguments3 = new COM("OpcLabs.EasyOpc.DataAccess.OperationModel.EasyDAItemSubscriptionArguments");
$ItemSubscriptionArguments3->ServerDescriptor->ServerClass = "OPCLabs.KitServer.2";
$ItemSubscriptionArguments3->ItemDescriptor->ItemID = "Trends.Sine (1 min)";
$ItemSubscriptionArguments3->GroupParameters->RequestedUpdateRate = 1000;

$ItemSubscriptionArguments4 = new COM("OpcLabs.EasyOpc.DataAccess.OperationModel.EasyDAItemSubscriptionArguments");
$ItemSubscriptionArguments4->ServerDescriptor->ServerClass = "OPCLabs.KitServer.2";
$ItemSubscriptionArguments4->ItemDescriptor->ItemID = "Simulation.Register_I4";
$ItemSubscriptionArguments4->GroupParameters->RequestedUpdateRate = 1000;

$arguments[0] = $ItemSubscriptionArguments1;
$arguments[1] = $ItemSubscriptionArguments2;
$arguments[2] = $ItemSubscriptionArguments3;
$arguments[3] = $ItemSubscriptionArguments4;

$Client = new COM("OpcLabs.EasyOpc.DataAccess.EasyDAClient");
$Events = new DEasyDAClientEvents();
com_event_sink($Client, $Events, "DEasyDAClientEvents");

$Client->SubscribeMultipleItems($arguments);

print "Processing item changed events for 1 minute...\n";
$startTime = time(); do { com_message_pump(1000); } while (time() < $startTime + 60);
// This example shows how to subscribe to changes of multiple items and display the value of the item with each change.

type
  TSubscribeMultipleItems_ClientEventHandlers = class
    // Item changed event handler
    procedure OnItemChanged(
      ASender: TObject;
      sender: OleVariant;
      const eventArgs: _EasyDAItemChangedEventArgs);
  end;

procedure TSubscribeMultipleItems_ClientEventHandlers.OnItemChanged(
  ASender: TObject;
  sender: OleVariant;
  const eventArgs: _EasyDAItemChangedEventArgs);
begin
  if eventArgs.Succeeded then
    WriteLn(eventArgs.Arguments.ItemDescriptor.ItemId, ': ', eventArgs.Vtq.ToString)
  else
    WriteLn(eventArgs.Arguments.ItemDescriptor.ItemId, ' *** Failure: ', eventArgs.ErrorMessageBrief);
end;

class procedure SubscribeMultipleItems.Main;
var
  Arguments: OleVariant;
  Client: TEasyDAClient;
  ClientEventHandlers: TSubscribeMultipleItems_ClientEventHandlers;
  HandleArray: OleVariant;
  ItemSubscriptionArguments1: _EasyDAItemSubscriptionArguments;
  ItemSubscriptionArguments2: _EasyDAItemSubscriptionArguments;
  ItemSubscriptionArguments3: _EasyDAItemSubscriptionArguments;
  ItemSubscriptionArguments4: _EasyDAItemSubscriptionArguments;
begin
  ItemSubscriptionArguments1 := CoEasyDAItemSubscriptionArguments.Create;
  ItemSubscriptionArguments1.ServerDescriptor.ServerClass := 'OPCLabs.KitServer.2';
  ItemSubscriptionArguments1.ItemDescriptor.ItemID := 'Simulation.Random';
  ItemSubscriptionArguments1.GroupParameters.RequestedUpdateRate := 1000;

  ItemSubscriptionArguments2 := CoEasyDAItemSubscriptionArguments.Create;
  ItemSubscriptionArguments2.ServerDescriptor.ServerClass := 'OPCLabs.KitServer.2';
  ItemSubscriptionArguments2.ItemDescriptor.ItemID := 'Trends.Ramp (1 min)';
  ItemSubscriptionArguments2.GroupParameters.RequestedUpdateRate := 1000;

  ItemSubscriptionArguments3 := CoEasyDAItemSubscriptionArguments.Create;
  ItemSubscriptionArguments3.ServerDescriptor.ServerClass := 'OPCLabs.KitServer.2';
  ItemSubscriptionArguments3.ItemDescriptor.ItemID := 'Trends.Sine (1 min)';
  ItemSubscriptionArguments3.GroupParameters.RequestedUpdateRate := 1000;

  ItemSubscriptionArguments4 := CoEasyDAItemSubscriptionArguments.Create;
  ItemSubscriptionArguments4.ServerDescriptor.ServerClass := 'OPCLabs.KitServer.2';
  ItemSubscriptionArguments4.ItemDescriptor.ItemID := 'Simulation.Register_I4';
  ItemSubscriptionArguments4.GroupParameters.RequestedUpdateRate := 1000;

  Arguments := VarArrayCreate([0, 3], varVariant);
  Arguments[0] := ItemSubscriptionArguments1;
  Arguments[1] := ItemSubscriptionArguments2;
  Arguments[2] := ItemSubscriptionArguments3;
  Arguments[3] := ItemSubscriptionArguments4;

  // Instantiate the client object and hook events
  Client := TEasyDAClient.Create(nil);
  ClientEventHandlers := TSubscribeMultipleItems_ClientEventHandlers.Create;
  Client.OnItemChanged := ClientEventHandlers.OnItemChanged;

  TVarData(HandleArray).VType := varArray or varVariant;
  TVarData(HandleArray).VArray := PVarArray(
    Client.SubscribeMultipleItems(Arguments));

  WriteLn('Processing item changed events for 1 minute...');
  PumpSleep(60*1000);

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

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

  WriteLn('Finished.');
  VarClear(HandleArray);
  VarClear(Arguments);
  FreeAndNil(Client);
  FreeAndNil(ClientEventHandlers);
end;
Rem This example shows how to subscribe to changes of multiple items and display the value of the item with each change.

Option Explicit

Dim ItemSubscriptionArguments1: Set ItemSubscriptionArguments1 = CreateObject("OpcLabs.EasyOpc.DataAccess.OperationModel.EasyDAItemSubscriptionArguments")
ItemSubscriptionArguments1.ServerDescriptor.ServerClass = "OPCLabs.KitServer.2"
ItemSubscriptionArguments1.ItemDescriptor.ItemID = "Simulation.Random"
ItemSubscriptionArguments1.GroupParameters.RequestedUpdateRate = 1000

Dim ItemSubscriptionArguments2: Set ItemSubscriptionArguments2 = CreateObject("OpcLabs.EasyOpc.DataAccess.OperationModel.EasyDAItemSubscriptionArguments")
ItemSubscriptionArguments2.ServerDescriptor.ServerClass = "OPCLabs.KitServer.2"
ItemSubscriptionArguments2.ItemDescriptor.ItemID = "Trends.Ramp (1 min)"
ItemSubscriptionArguments2.GroupParameters.RequestedUpdateRate = 1000

Dim ItemSubscriptionArguments3: Set ItemSubscriptionArguments3 = CreateObject("OpcLabs.EasyOpc.DataAccess.OperationModel.EasyDAItemSubscriptionArguments")
ItemSubscriptionArguments3.ServerDescriptor.ServerClass = "OPCLabs.KitServer.2"
ItemSubscriptionArguments3.ItemDescriptor.ItemID = "Trends.Sine (1 min)"
ItemSubscriptionArguments3.GroupParameters.RequestedUpdateRate = 1000

Dim ItemSubscriptionArguments4: Set ItemSubscriptionArguments4 = CreateObject("OpcLabs.EasyOpc.DataAccess.OperationModel.EasyDAItemSubscriptionArguments")
ItemSubscriptionArguments4.ServerDescriptor.ServerClass = "OPCLabs.KitServer.2"
ItemSubscriptionArguments4.ItemDescriptor.ItemID = "Simulation.Register_I4"
ItemSubscriptionArguments4.GroupParameters.RequestedUpdateRate = 1000

Dim arguments(3)
Set arguments(0) = ItemSubscriptionArguments1
Set arguments(1) = ItemSubscriptionArguments2
Set arguments(2) = ItemSubscriptionArguments3
Set arguments(3) = ItemSubscriptionArguments4

Dim Client: Set Client = CreateObject("OpcLabs.EasyOpc.DataAccess.EasyDAClient")
WScript.ConnectObject Client, "Client_"

Client.SubscribeMultipleItems arguments

WScript.Echo "Processing item changed events for 1 minute..."
WScript.Sleep 60*1000

Sub Client_ItemChanged(Sender, e)
    If Not (e.Succeeded) Then
        WScript.Echo "*** Failure: " & e.ErrorMessageBrief
        Exit Sub
    End If

    WScript.Echo e.Arguments.ItemDescriptor.ItemId & ": " & e.Vtq
End Sub

 

Common considerations

Note: It is NOT an error to subscribe to the same item twice (or more times), even with precisely the same parameters. You will receive separate subscription handles, and with regard to your application, this situation will look no different from subscribing to different items. Internally, however, the subscription made to the OPC server will be optimized (merged together) if possible.

Percent deadband

You can specify percent deadband when subscribing to an OPC item.

// This example shows how subscribe to changes of a single item with percent deadband.

using System;
using System.Diagnostics;
using System.Threading;
using OpcLabs.BaseLib.ComInterop;
using OpcLabs.EasyOpc.DataAccess;

namespace DocExamples.DataAccess._EasyDAClient
{
    partial class SubscribeItem
    {
        public static void PercentDeadband()
        {
            // Instantiate the client object.
            var client = new EasyDAClient();

            const float percentDeadband = 5.0f;
            Console.WriteLine($"Subscribing with {percentDeadband}% deadband...");
            // The callback is a lambda expression the displays the value
            client.SubscribeItem("", "OPCLabs.KitServer.2", "Simulation.Ramp 0:100 (10 s)", 
                VarTypes.Empty, requestedUpdateRate:100, percentDeadband:percentDeadband, 
                (sender, eventArgs) =>
                {
                    Debug.Assert(!(eventArgs is null));

                    if (eventArgs.Succeeded)
                    {
                        Debug.Assert(!(eventArgs.Vtq is null));
                        Console.WriteLine(eventArgs.Vtq.ToString());
                    }
                    else
                        Console.WriteLine("*** Failure: {0}", eventArgs.ErrorMessageBrief);
                },
                state:null);

            Console.WriteLine("Processing item changed events for 10 seconds...");
            Thread.Sleep(10 * 1000);

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

            Console.WriteLine("Waiting for 2 seconds...");
            Thread.Sleep(2 * 1000);
        }
    }
}

You can also specify the percent deadband when subscribing to multiple OPC items, and the percent deadband can be different for each item.

// This example shows how subscribe to changes of multiple items with percent deadband.

using System;
using System.Threading;
using OpcLabs.BaseLib.ComInterop;
using OpcLabs.EasyOpc.DataAccess;
using OpcLabs.EasyOpc.DataAccess.OperationModel;

namespace DocExamples.DataAccess._EasyDAClient
{
    partial class SubscribeMultipleItems
    {
        public static void PercentDeadband()
        {
            // Instantiate the client object.
            using (var client = new EasyDAClient())
            {
                client.ItemChanged += client_PercentDeadband_ItemChanged;

                Console.WriteLine("Subscribing with different percent deadbands...");
                client.SubscribeMultipleItems(
                    new[] {
                            new DAItemGroupArguments("", "OPCLabs.KitServer.2", "Simulation.Ramp 0:100 (10 s)", 
                                VarTypes.Empty, requestedUpdateRate:100, percentDeadband:10.0f, null), 
                            new DAItemGroupArguments("", "OPCLabs.KitServer.2", "Simulation.Ramp 0:100 (1 min)",
                                VarTypes.Empty, requestedUpdateRate:100, percentDeadband:5.0f, null),
                        });

                Console.WriteLine("Processing item changed events for 1 minute...");
                Thread.Sleep(60 * 1000);
            }
        }

        // Item changed event handler
        static void client_PercentDeadband_ItemChanged(object sender, EasyDAItemChangedEventArgs e)
        {
            if (e.Succeeded)
                Console.WriteLine("{0}: {1}", e.Arguments.ItemDescriptor.ItemId, e.Vtq);
            else
                Console.WriteLine("{0} *** Failure: {1}", e.Arguments.ItemDescriptor.ItemId, e.ErrorMessageBrief);
        }
    }
}

 

More examples

The example below logs OPC Data Access item changes into an XML file.

The main program:

// Logs OPC Data Access item changes into an XML file.

using System;
using System.Diagnostics;
using System.Xml;
using System.Xml.Serialization;
using OpcLabs.EasyOpc.DataAccess;
using OpcLabs.EasyOpc.DataAccess.OperationModel;

namespace XmlLogger
{
    class Program
    {
        static void Main()
        {
            Console.WriteLine("Starting up...");
            var xmlSerializer = new XmlSerializer(typeof(EasyDAItemChangedEventArgs));
            var xmlWriter = XmlWriter.Create("OpcData.xml", new XmlWriterSettings
                                        {
                                            Indent = true,
                                            CloseOutput = true
                                        });
            // The root element can have any name you need, but the name below also allows reading the log back as .NET array
            xmlWriter.WriteStartElement("ArrayOfEasyDAItemChangedEventArgs");

            Console.WriteLine("Logging data for 30 seconds...");
            int handle = EasyDAClient.SharedInstance.SubscribeItem("", "OPCLabs.KitServer.2", 
                "Simulation.Incrementing (1 s)", 100,
               (_, eventArgs) =>
                   {
                       Debug.Assert(!(eventArgs is null));
                       Console.Write(".");
                       xmlSerializer.Serialize(xmlWriter, eventArgs);
                   });
            System.Threading.Thread.Sleep(30 * 1000);

            Console.WriteLine();
            Console.WriteLine("Shutting down...");
            EasyDAClient.SharedInstance.UnsubscribeItem(handle);
            xmlWriter.WriteEndElement();    // not really necessary - XmlWriter would write the end tag for us anyway
            xmlWriter.Close();

            Console.WriteLine("Finished.");
        }
    }
}
' Logs OPC Data Access item changes into an XML file.

Imports System.Xml
Imports System.Xml.Serialization
Imports OpcLabs.EasyOpc.DataAccess
Imports OpcLabs.EasyOpc.DataAccess.OperationModel


Friend Class Program
    Shared WithEvents _client As New EasyDAClient
    Shared ReadOnly XmlSerializer As New XmlSerializer(GetType(EasyDAItemChangedEventArgs))
    Shared _xmlWriter As XmlWriter

    Shared Sub Main()
        Console.WriteLine("Starting up...")
        _xmlWriter = XmlWriter.Create("OpcData.xml", New XmlWriterSettings With {.Indent = True, .CloseOutput = True})
        ' The root element can have any name you need, but the name below also allows reading the log back as .NET array
        _xmlWriter.WriteStartElement("ArrayOfEasyDAItemChangedEventArgs")

        Console.WriteLine("Logging data for 30 seconds...")
        Dim handle As Integer = _client.SubscribeItem(
            "",
            "OPCLabs.KitServer.2",
            "Simulation.Incrementing (1 s)",
            100)
        Threading.Thread.Sleep(30 * 1000)

        Console.WriteLine()
        Console.WriteLine("Shutting down...")
        _client.UnsubscribeItem(handle)
        _xmlWriter.WriteEndElement() ' not really necessary - XmlWriter would write the end tag for us anyway
        _xmlWriter.Close()

        Console.WriteLine("Finished.")
    End Sub

    Private Shared Sub ItemChanged(ByVal sender As Object, ByVal eventArgs As EasyDAItemChangedEventArgs) Handles _client.ItemChanged
        Debug.Assert(eventArgs IsNot Nothing)
        Console.Write(".")
        XmlSerializer.Serialize(_xmlWriter, eventArgs)
    End Sub
End Class

 

 

See Also

Recommended

Knowledge Base

Installed Examples - Console

Installed Examples - WindowsForms

Installed Examples - WindowsService

Examples - OPC Data Access

Examples - OPC XML-DA