Skip to content

How-to guide

OPC Classic

Access OPC DA, OPC AE, and OPC HDA servers with the same OpcClient: features, nodes, remote servers over DCOM, and server discovery.

This content was machine translated and may contain errors. Read in German

How-to guide

OPC Classic ​

With an address of the form opc.com://, the OpcClient connects to an OPC Classic server. Internally, it starts an OPC UA wrapper server that provides each specification supported by the Classic server (DA, AE, and HDA) as a separate area of its address space. From then on, your application works only with the familiar OPC UA API: reading, writing, subscribing, events, acknowledging, and historical data use the same methods as with an OPC UA server. If you later switch to an OPC UA server, only the address in your code changes.

Prerequisites: The client runs on Windows, the OPC Classic runtime is installed, and the application uses the Opc.UaFx.Advanced package. The section Connect to an OPC Classic server in "Getting started" shows how the client establishes the connection and how you build the address.

What OPC Classic supports ​

Classic specificationSupportedSDK methodsDescription
OPC DA (Data Access)read, write, subscribe, browseReadNode(s), WriteNode(s), SubscribeDataChange, BrowseNodeRead and write values, Subscriptions, Browse nodes
OPC AE (Alarms & Events)receive events, acknowledge conditions, request the current stateSubscribeEvent, Acknowledge, RefreshConditionsEvents, Alarms and conditions
OPC HDA (Historical Data Access)read raw values, values at specific times, and aggregated values, modify and delete valuesReadNodeHistory, ReadNodeHistoryAtTime, ReadNodeHistoryProcessed, UpdateNodeHistory, DeleteNodeHistoryHistorical data

The client determines which specifications a server actually supports when it connects. The section Speed up the connection shows how to make this faster.

Nodes of a Classic server ​

Types usedOpcClientOpcNamespaceOpcNodeInfoOpcNodeId

The wrapper creates a separate namespace for each specification. Its URI is the address of the Classic server with the suffix /DA, /AE, or /HDA. The index of these namespaces depends on the order of the specifications, so determine it at runtime through client.Namespaces instead of hard-coding it. The most reliable way to find the nodes themselves is to browse:

csharp
foreach (OpcNamespace ns in client.Namespaces)
    Console.WriteLine("{0}: {1}", ns.Index, ns.Value);
 
OpcNodeInfo objects = client.BrowseNode(OpcObjectTypes.ObjectsFolder);
 
foreach (OpcNodeInfo node in objects.Children())
    Console.WriteLine("{0} ({1})", node.DisplayName.Value, node.NodeId);
vbnet
For Each ns As OpcNamespace In client.Namespaces
    Console.WriteLine("{0}: {1}", ns.Index, ns.Value)
Next

Dim objects As OpcNodeInfo = client.BrowseNode(OpcObjectTypes.ObjectsFolder)

For Each node As OpcNodeInfo In objects.Children()
    Console.WriteLine("{0} ({1})", node.DisplayName.Value, node.NodeId)
Next

The wrapper builds the identifiers of the items from the ItemID of the Classic server. With OPC DA, an item identifier in the DA namespace looks like this, for example: 0:Channel1.Device1.Tag1. The prefix is a convention of the wrapper. For this reason, take identifiers from a browse result instead of building them yourself for other specifications.

Read, write, and subscribe to values (DA) ​

Types usedOpcClientOpcNamespaceOpcNodeIdOpcValueOpcStatusOpcSubscription

With the identifier of a DA item, all read, write, and subscription methods are available. Use an item identifier from the DA namespace like any other NodeId:

csharp
OpcNamespace da = null;
 
foreach (OpcNamespace ns in client.Namespaces) {
    if (ns.Value.EndsWith("/DA"))
        da = ns;
}
 
var tag = new OpcNodeId("0:Channel1.Device1.Tag1", namespaceIndex: da.Index);
 
OpcValue value = client.ReadNode(tag);
OpcStatus result = client.WriteNode(tag, value: 42);
 
OpcSubscription subscription = client.SubscribeDataChange(tag, HandleDataChanged);
vbnet
Dim da As OpcNamespace = Nothing

For Each ns As OpcNamespace In client.Namespaces
    If ns.Value.EndsWith("/DA") Then
        da = ns
    End If
Next

Dim tag = New OpcNodeId("0:Channel1.Device1.Tag1", namespaceIndex:=da.Index)

Dim value As OpcValue = client.ReadNode(tag)
Dim result As OpcStatus = client.WriteNode(tag, value:=42)

Dim subscription As OpcSubscription = client.SubscribeDataChange(tag, AddressOf HandleDataChanged)

Browse older DA servers ​

Types usedOpcClientOpcClientInterfacesOpcClassicDataAccessOptionsOpcClassicOrganization

Some older DA servers report the organization of their address space incorrectly or do not support jumping directly to a branch (BrowseTo). A symptom is that browsing returns no items or stops. Use client.Interfaces.DataAccess to set the behavior before you connect:

  • Organization: Query (default) asks the server for the organization, Flat treats the address space as a single level, Hierarchical as a tree.
  • SupportsBrowseTo: true (default). With false, the wrapper does not use BrowseTo.
csharp
client.Interfaces.DataAccess.Organization = OpcClassicOrganization.Flat;
client.Interfaces.DataAccess.SupportsBrowseTo = false;
 
client.Connect();
vbnet
client.Interfaces.DataAccess.Organization = OpcClassicOrganization.Flat
client.Interfaces.DataAccess.SupportsBrowseTo = False

client.Connect()

Historical data (HDA) ​

Types usedOpcClientOpcNodeIdOpcHistoryValueOpcAggregateType

The items of an HDA server are located in the HDA namespace. The client reads their historical data with the same API as with an OPC UA server, including aggregated values. Determine the identifier of the item by browsing, as shown above:

csharp
DateTime endTime = DateTime.UtcNow;
DateTime startTime = endTime.AddHours(-1);
 
var raw = client.ReadNodeHistory(startTime, endTime, hdaItemId);
var average = client.ReadNodeHistoryProcessed(
        startTime,
        endTime,
        OpcAggregateType.Average,
        hdaItemId);
 
foreach (var historyValue in raw)
    Console.WriteLine("{0}: {1}", historyValue.Timestamp, historyValue.Value);
vbnet
Dim endTime As DateTime = DateTime.UtcNow
Dim startTime As DateTime = endTime.AddHours(-1)

Dim raw = client.ReadNodeHistory(startTime, endTime, hdaItemId)
Dim average = client.ReadNodeHistoryProcessed( _
        startTime, _
        endTime, _
        OpcAggregateType.Average, _
        hdaItemId)

For Each historyValue In raw
    Console.WriteLine("{0}: {1}", historyValue.Timestamp, historyValue.Value)
Next

The wrapper also maps modifying and deleting historical values (UpdateNodeHistory, DeleteNodeHistory, and others) to the HDA interfaces, as far as the server supports them. The page Historical data describes these methods.

Alarms and events (AE) ​

Types usedOpcClientOpcSubscriptionOpcEventReceivedEventArgsOpcAcknowledgeableCondition

The client receives the events and conditions of an AE server through a subscription on the Server object. Acknowledgeable conditions arrive as OpcAcknowledgeableCondition, and you acknowledge them with Acknowledge. The wrapper forwards the acknowledgment to the AE server. RefreshConditions requests the current state of all conditions:

csharp
OpcSubscription subscription = client.SubscribeEvent(
        OpcObjectTypes.Server,
        HandleEventReceived);
 
subscription.RefreshConditions();
...
private void HandleEventReceived(object sender, OpcEventReceivedEventArgs e)
{
    var condition = e.Event as OpcAcknowledgeableCondition;
 
    if (condition != null && !condition.IsAcked)
        condition.Acknowledge(client, comment: "Acknowledged by operator");
}
vbnet
Dim subscription As OpcSubscription = client.SubscribeEvent( _
        OpcObjectTypes.Server, _
        AddressOf HandleEventReceived)

subscription.RefreshConditions()
...
Private Sub HandleEventReceived(ByVal sender As Object, ByVal e As OpcEventReceivedEventArgs)
    Dim condition = TryCast(e.Event, OpcAcknowledgeableCondition)

    If condition IsNot Nothing AndAlso Not condition.IsAcked Then
        condition.Acknowledge(client, comment:="Acknowledged by operator")
    End If
End Sub

The areas and sources of the AE server appear in the AE namespace. Which event types a server reports depends on the server. The pages Events and Alarms and conditions describe how to work with events and conditions.

Remote servers and DCOM security ​

Types usedOpcClientOpcClientSecurityOpcClientIdentityOpcClassicAuthenticationLevelOpcClassicImpersonationLevel

If the Classic server runs on another computer, the address contains the name of that computer. Communication then runs over DCOM. If you set an OpcClientIdentity through client.Security.UserIdentity, the client uses it as the Windows logon for DCOM. You can set the DCOM security in code instead of configuring it with dcomcnfg:

csharp
var client = new OpcClient("opc.com://remote-host/{E4EBF7FA-CCAC-4125-A611-EAC4981C00EA}");
 
client.Security.UserIdentity = new OpcClientIdentity(userName: "opcuser", password: "secret");
client.Security.AuthenticationLevel = OpcClassicAuthenticationLevel.PacketIntegrity;
client.Security.ImpersonationLevel = OpcClassicImpersonationLevel.Impersonate;
 
client.Connect();
vbnet
Dim client = New OpcClient("opc.com://remote-host/{E4EBF7FA-CCAC-4125-A611-EAC4981C00EA}")

client.Security.UserIdentity = New OpcClientIdentity(userName:="opcuser", password:="secret")
client.Security.AuthenticationLevel = OpcClassicAuthenticationLevel.PacketIntegrity
client.Security.ImpersonationLevel = OpcClassicImpersonationLevel.Impersonate

client.Connect()
Property of OpcClientSecurityValue used if not set
AuthenticationLevelOpcClassicAuthenticationLevel.PacketIntegrity
ImpersonationLevelOpcClassicImpersonationLevel.Impersonate
AuthenticationServiceOpcClassicAuthenticationService.WinNt
AuthorizationServiceOpcClassicAuthorizationService.None
ProxyCapabilitiesOpcClassicProxyCapabilities.None

If one of these properties is set, the client uses it to initialize the DCOM security for the entire application. Otherwise, the DCOM security stays unchanged. The default PacketIntegrity follows the Microsoft recommendations for DCOM hardening, see DCOM hardening (CVE-2021-26414).

Find servers ​

Types usedOpcClassicDiscoveryClientOpcClassicIdentityOpcClassicEndpointDescriptionOpcClassicInterface

The OpcClassicDiscoveryClient searches for Classic servers locally or on a remote computer, using an OpcClassicIdentity for the logon on the remote computer. DiscoverEndpoints returns the description of each server with its address (Server.Uri) and the specifications it supports:

csharp
var identity = new OpcClassicIdentity(userName: "opcuser", password: "secret");
 
using (var discovery = new OpcClassicDiscoveryClient(host: "remote-host", identity)) {
    foreach (OpcClassicEndpointDescription endpoint in discovery.DiscoverEndpoints()) {
        Console.WriteLine("{0}: {1}", endpoint.Server.Name, endpoint.Server.Uri);
 
        foreach (OpcClassicInterface serverInterface in endpoint.Interfaces)
            Console.WriteLine("  {0}", serverInterface);
    }
}
vbnet
Dim identity = New OpcClassicIdentity(userName:="opcuser", password:="secret")

Using discovery = New OpcClassicDiscoveryClient(host:="remote-host", identity)
    For Each endpoint As OpcClassicEndpointDescription In discovery.DiscoverEndpoints()
        Console.WriteLine("{0}: {1}", endpoint.Server.Name, endpoint.Server.Uri)

        For Each serverInterface As OpcClassicInterface In endpoint.Interfaces
            Console.WriteLine("  {0}", serverInterface)
        Next
    Next
End Using

With a specification as a filter, DiscoverServers returns only the servers that support it. Without a filter, the client queries all specifications. On computers with a large number of COM applications, the filter is much faster:

csharp
using (var discovery = new OpcClassicDiscoveryClient(host: "remote-host", identity)) {
    OpcClassicApplicationDescriptionCollection servers
            = discovery.DiscoverServers(OpcClassicInterfaces.Da30);
}
vbnet
Using discovery = New OpcClassicDiscoveryClient(host:="remote-host", identity)
    Dim servers As OpcClassicApplicationDescriptionCollection _
            = discovery.DiscoverServers(OpcClassicInterfaces.Da30)
End Using

Speed up the connection ​

Types usedOpcClientOpcClassicInterfaceCollectionOpcClassicEndpointDescription

When connecting, the client first queries which specifications the server supports. If you already know them, for example from DiscoverEndpoints, specify them in advance through ServerInterfaces. The client then skips this query, and the connection is established noticeably faster:

csharp
var client = new OpcClient(endpoint.Server.Uri);
 
foreach (OpcClassicInterface serverInterface in endpoint.Interfaces)
    client.ServerInterfaces.Add(serverInterface);
 
client.Connect();
vbnet
Dim client = New OpcClient(endpoint.Server.Uri)

For Each serverInterface As OpcClassicInterface In endpoint.Interfaces
    client.ServerInterfaces.Add(serverInterface)
Next

client.Connect()

Monitor the connection ​

Types usedOpcClientOpcClientInterfacesOpcClientStateChangedEventArgs

By default, the state of the client does not reflect a loss of the DCOM connection to the Classic server. With client.Interfaces.UseBreakMonitoring = true, the client checks the connection to the Classic server and reflects a break (RPC_S_SERVER_UNAVAILABLE) in OpcClient.State. The wrapper server stops, the client reports the connection loss, and it reconnects as soon as the Classic server is reachable again, just as with OPC UA. The option is off by default because it creates a second instance of the COM server. With some servers, this can have side effects:

csharp
client.Interfaces.UseBreakMonitoring = true;
client.StateChanged += HandleStateChanged;
 
client.Connect();
...
private static void HandleStateChanged(
        object sender,
        OpcClientStateChangedEventArgs e)
{
    Console.WriteLine("{0} -> {1}", e.OldState, e.NewState);
}
vbnet
client.Interfaces.UseBreakMonitoring = True
AddHandler client.StateChanged, AddressOf HandleStateChanged

client.Connect()
...
Private Shared Sub HandleStateChanged( _
        ByVal sender As Object, _
        ByVal e As OpcClientStateChangedEventArgs)
    Console.WriteLine("{0} -> {1}", e.OldState, e.NewState)
End Sub

The page Long-running clients describes how the client handles connection breaks and reconnects.