# FAQ

> Reference: Answers to frequently asked questions about the OPC UA .NET SDK: debugging, PowerShell, NuGet, exceptions and licensing, S7 PLCs, node types, and security.

> This content was machine translated and may contain errors. [Read in German](https://www.traeger.de/docs/development/opcua/faqs)

## Development

### Connection loss while debugging

**Problem:** After a short interruption or while stepping through the application, the client loses the connection. The exception `BadServerNotConnected` occurs with the message "The operation could not complete because the client is not connected to the server."

Client and server use keep-alive logic to check whether their connection still exists: they must exchange at least one message within a time window that timeout settings define. When the debugger pauses the application (breakpoint or stepping), the client sends no requests during this time. If the pause exceeds the time window, the connection counts as lost.

For debug sessions, use larger timeouts, in particular a larger value for `OpcClient.SessionTimeout` (default 60000 ms). `OpcClient.KeepAlive.Interval` defines how often the client checks the connection (default 5000 ms). `OpcClient.OperationTimeout` limits the maximum duration of a single request (default 120000 ms). The section [Connection events at a glance](https://www.traeger.de/en/docs/development/opcua/client/long-running-clients#connection-events-at-a-glance) on the page "Long-running clients" describes how the client detects a connection loss and which settings apply.

> **In detail**
> Technical background of the communication between client and server:
>
> In this model, there is always an active and a passive participant. Apart from reverse connect, the server is always the passive participant and listens for incoming connections. The client is the active participant and connects to an endpoint that the server provides to communicate with it. If the connection between the participants is lost for any reason, the client must re-establish a connection to the server.
>
> This raises the following questions:
>
> - How does a client detect that a connection has been lost?
>   - Is it physically disconnected, that is, really cut off?
>   - Is the server's response time delayed?
>   - Is the network overloaded, so that responses barely get through?
> - When does a client detect that the connection has been lost?
>   - Has the current request gone unanswered for too long?
>   - Is the server's response to one of the last requests too old?
>   - Is the network, and with it the server, no longer reachable?
>
> The same questions arise on the server side:
> - How does a server detect that a client is still connected?
>   - Does the physical connection still exist?
>   - Is the last request from the client still current?
>   - Is the network overloaded, so that requests barely get through?
> - When does a server detect that a particular client connection has been lost?
>   - Was the last request too long ago?
>   - Is the client's request to one of the last responses too old?
>   - Is the network, and with it the client, no longer reachable?
>
> Client and server cannot do anything about a connection loss at the physical level. The logical level is different. The OSI model has several layers at which the state of a connection can be checked. Whichever layer the check works on, the procedure is always the same: the client must send requests and the server must answer them. Only this establishes with 100% certainty that the connection is still intact enough for further communication.
>
> How do client and server recognize when a connection counts as broken?
> 1. The client or server cannot send a request or response.
> 1. The last request or response was too long ago and is therefore too old.
> 1. The client no longer sends requests, or the server no longer answers them.
>
> Regardless of which case the participants check, client and server must tie these cases to specific conditions. Time information serves this purpose:
> - On the client side, this means:
>   - The time at which a request was sent.
>   - The time at which a response was received.
> - On the server side, this means:
>   - The time since the last request received.
>   - The time since the last response sent.
>
> If the participants add the current timestamp to requests and responses, each can check whether it can still expect a response or another request. The timestamp is the moment at which a participant sends the message. If the time offset since the last response or request is too large, the participant can assume a connection loss. Timeout settings define how large this time offset may be. They apply either specifically to the client or the server, or client and server negotiate them with each other.
>
> Without timeout conditions for the connection, a client waits indefinitely for a response, and a server reserves an endpoint for the client indefinitely. The client stays blocked until the unlikely response arrives. The server runs out of available endpoints for other clients and ties up important system resources.
>
> Regardless of how and by whom the time window is defined, both participants must be guaranteed to have exchanged at least one message within the smallest overall time window. The logic for this is called keep-alive logic. The active participant is responsible for signaling to the passive participant that the connection still exists. For this, every protocol with keep-alive defines that a request is to be sent and answered within a certain interval (that is, a time window). This is either a specific, lightweight request or any request. The passive participant then uses this request, or the last request in general, to recognize whether the other participant is still reachable and connected. It also knows from this whether the connection to it is still intact. The active participant, in turn, knows just as surely from the response within the time window that the connection still exists.

### Use the SDK in PowerShell

For the OPC UA .NET SDK, there is a sample including a bootstrapper on GitHub:
[OPC UA .NET Samples: PowerShell Client](https://github.com/Traeger-GmbH/opcuanet-samples/tree/master/ps)

### Update after NuGet preview fails

NuGet stores installed packages in different places depending on the project format. In projects that are not SDK-style, they are in the `packages` folder next to the Visual Studio solution. In SDK-style projects, they are in the local NuGet cache. To update to the official version, delete the preview packages from your own NuGet source and from the local NuGet cache. Where the local NuGet cache is located depends on your project.

- If you reference the SDK through NuGet with a `packages.config` file:
  - Close Visual Studio
  - In Explorer, navigate to the project directory
  - Delete the `packages` directory next to your solution
  - Open the project and build it again
- If you reference the SDK through NuGet without a `packages.config` file:
  - Close Visual Studio
  - Navigate to the local NuGet cache: `C:\Users\username\.nuget\packages`
  - Delete the `Opc.UaFx.Client` or `Opc.UaFx.Advanced` directory
  - Open the project and build it again

You can then update to the current version or install it through the NuGet client as usual.

### TypeInitializationException at startup

This error can occur if Visual Studio has not updated all binaries in use in the project directory after an update to a newer version of the SDK. Perform the following steps:
- Close Visual Studio
- In Explorer, navigate to the project directory
- Delete all `bin` and `obj` directories
- Open the project and build it again

If the error still occurs at runtime, the following steps should solve the problem:
- If you reference the SDK from the ZIP archive, check:
  - whether all files of the target framework folder are referenced in the project
  - whether the files are referenced from the correct target framework folder
- If you reference the SDK through NuGet with a `packages.config` file:
  - Close Visual Studio
  - In Explorer, navigate to the project directory
  - Delete the `packages` directory next to your solution
  - Open the project and build it again
- If you reference the SDK through NuGet without a `packages.config` file:
  - Close Visual Studio
  - Navigate to the local NuGet cache: `C:\Users\username\.nuget\packages`
  - Delete the `Opc.UaFx.Client` or `Opc.UaFx.Advanced` directory
  - Open the project and build it again
- If you reference the SDK in a project with `app.config` and one of the `InnerExceptions` is a `FileLoadException`:
  - Open the `app.config` file and check the `bindingRedirect` entries
  - Remove all entries for the assembly named in the exception from the `app.config`
  - Build the project again

If none of these approaches solves the problem, send a description of the problem with details on the exception or its `InnerException` to [support@traeger.de](mailto:support@traeger.de).

### Client and server in one application

This works without problems. Many of our tests are based on code snippets like the following:

```csharp
using (var server = new OpcServer()) {
    server.Start();

    using (var client = new OpcClient(server.Address)) {
        client.Connect();
        ...
    }
}
```

```vbnet
Using server = New OpcServer()
    server.Start()

    Using client = New OpcClient(server.Address)
        client.Connect()
        ...
    End Using
End Using
```

### Delayed notifications (S7)

Types used: `OpcSubscription`, `OpcMonitoredItem`, `OpcMonitoredItemStatus`

Several properties let you check whether a server supports the requested intervals and whether they take effect:
- Check the PublishingInterval through the `CurrentPublishingInterval` property.
- Check the SamplingInterval through the `Status.SamplingInterval` property.

Often, the main problem lies in the server configuration. Depending on the S7 PLC, the server supports only certain minimum values for the settings of subscriptions and MonitoredItems. According to Siemens, the following minimum values apply:

| Property | CPU 1510, 1511, 1512, 1513 | CPU 1515, 1516, 1505S | CPU 1517, 1518, 1507S |
|---|---|---|---|
| Maximum number of subscriptions with more than 1000 MonitoredItems per subscription across all sessions | 10 | 10 | 10 |
| Maximum number of subscriptions with at most 1000 MonitoredItems per subscription across all sessions | 20 | 20 | 20 |
| Maximum number of subscriptions per session | 20 | 20 | 20 |
| Smallest possible sampling interval | 100 ms | 100 ms | 10 ms |
| Smallest possible publishing interval | 500 ms | 200 ms | 10 ms |
| Maximum number of sessions | 32 | 48 | 64 |
| Recommended maximum number of MonitoredItems across all subscriptions | 1000 | 1000 (1505S), 2000 | 10000 |

Source: [Which system limits does the OPC UA server of the S7-1500 with firmware V2.8.x have?](https://support.industry.siemens.com/cs/document/109755846/welche-systemgrenzen-enth%C3%A4lt-der-opc-ua-server-bei-der-s7-1500-mit-der-firmware-v2-8-x-?dti=0&lc=de-DE)

You can check which limits a server supports through several nodes in the path `/Objects/Server/ServerCapabilities` and, below it, in the `OperationLimits` node. A client application has no influence on these settings.

## License

### LicenseException after 30 minutes

Without a license key, the OPC UA .NET SDK runs in evaluation mode: for 30 minutes per application launch, without restrictions for client and server development. If 30 minutes per application start are not enough for you, you can request an extended evaluation from us free of charge. To do so, contact our support at [support@traeger.de](mailto:support@traeger.de) or get advice from us directly. Our development team also answers open questions. The page [Set up the license](https://www.traeger.de/en/docs/development/opcua/license-setup) describes how to enter the license key.

### BadLicenseExpired after 30 minutes

A server that is already running when the evaluation expires keeps running, but rejects every request of its clients with the status `BadLicenseExpired`.

Without a license key, the OPC UA .NET SDK runs in evaluation mode: for 30 minutes per application launch, without restrictions for client and server development. If 30 minutes per application start are not enough for you, you can request an extended evaluation from us free of charge. To do so, contact our support at [support@traeger.de](mailto:support@traeger.de) or get advice from us directly. Our development team also answers open questions. The page [Set up the license](https://www.traeger.de/en/docs/development/opcua/license-setup) describes how to enter the license key.

## Nodes

### OpcObjectNode or OpcFolderNode?

Types used: `OpcObjectNode`, `OpcFolderNode`

The difference is semantic. The class `OpcFolderNode` is a subclass of `OpcObjectNode`. They are used as follows:
- `OpcObjectNode` groups nodes that are also **physically** related to each other.
- `OpcFolderNode` groups nodes that are rather **logically** related to each other.

> **In detail**
> The documentation of the class `OpcObjectNode` states:
> > Defines a logical unit to represent more complex information than `OpcVariableNode`. Objects are used to represent systems, system components, real-world objects, and software objects.
>
> > From a more abstract point of view, objects are used to group variables and other objects in the address space. Objects should therefore be used when some common structures or groups of objects and/or variables are to be described.
> >
> > Simple objects with only one value (for example, a simple heat sensor) can also be modeled as variables. However, extension mechanisms should be considered (for example, a complex heat sensor subtype could have several values), as well as whether this object is to be exposed as an object in the client's GUI or only as a value. Whenever a modeler is in doubt as to which solution to use, the object with one variable should be preferred.
>
> The documentation of the class `OpcFolderNode` states:
>
> > Defines a node to organize the address space in a hierarchy of nodes. They represent the root node of a subtree and have no other semantics.
>
> Because `OpcFolderNode` is a subclass of `OpcObjectNode`, there is no direct structural difference between the two types. The reference type through which child nodes are attached does not differ by default either: both use HasComponent. `OpcFolderNode` also passes this reference type on to its direct child nodes. If a child node is to be attached through Organizes, set this explicitly through its `ReferenceTypeId` property (`OpcReferenceTypes.Organizes`). The difference lies in the type definition: an `OpcFolderNode` is of type FolderType. Simply put:
> - `OpcObjectNode` groups nodes that are also **physically** related to each other.
> - `OpcFolderNode` groups nodes that are rather **logically** related to each other.

### Change the separator in NodeIds

Types used: `OpcNodeId`, `OpcNominalNodeIdFactory`

To do this, change the default NodeIdFactory:

```csharp
OpcNodeId.Factory = new OpcNominalNodeIdFactory() {
    Separator = '.'
};
```

```vbnet
OpcNodeId.Factory = New OpcNominalNodeIdFactory With {
    .Separator = "."c
}
```

### Multiple return values of a method

Types used: `OpcMethodNode`, `OpcArgumentAttribute`

A method returns multiple values through `return`, `ref`, and `out` values. Note that `ref` values serve as both input and output arguments. You can define such an `OpcMethodNode` like this, for example:

```csharp
private delegate void CalculateDelegate(
        int a,
        int b,
        ref int c,
        out int additionResult,
        out int differenceResult);

private void Calculate(
        [OpcArgument("a", Description = "The left operand.")]
        int a,
        [OpcArgument("b", Description = "The right operand.")]
        int b,
        [OpcArgument("c", Description = "Factor")]
        ref int c,
        [OpcArgument("add", Description = "The result of addition.")]
        out int additionResult,
        [OpcArgument("diff", Description = "The result of difference.")]
        out int differenceResult)
{
    additionResult = (a + b) * c;
    differenceResult = (a - b) * c;
}

this.methodNode = new OpcMethodNode(
        this.folderNode,
        nameof(Calculate),
        new CalculateDelegate(this.Calculate));
```

```vbnet
Private Delegate Sub CalculateDelegate( _
        ByVal a As Integer, _
        ByVal b As Integer, _
        ByRef c As Integer, _
        <Out> ByRef additionResult As Integer, _
        <Out> ByRef differenceResult As Integer)

Private Sub Calculate( _
        <OpcArgument("a", Description:="The left operand.")> _
        ByVal a As Integer, _
        <OpcArgument("b", Description:="The right operand.")> _
        ByVal b As Integer, _
        <OpcArgument("c", Description:="Factor")> _
        ByRef c As Integer, _
        <OpcArgument("add", Description:="The result of addition."), Out> _
        ByRef additionResult As Integer, _
        <OpcArgument("diff", Description:="The result of difference."), Out> _
        ByRef differenceResult As Integer)
    additionResult = (a + b) * c
    differenceResult = (a - b) * c
End Sub

Me.methodNode = New OpcMethodNode( _
        Me.folderNode, _
        NameOf(Calculate), _
        New CalculateDelegate(AddressOf Me.Calculate))
```

### Restrict the data type on write

Types used: `OpcVariableNode`, `OpcVariableValue`, `OpcWriteVariableValueContext`, `OpcWriteVariableValueCallback`

A custom callback that checks the value to be written restricts the data type. Such a callback can look like this:

```csharp
this.variableNode.WriteVariableValueCallback = this.WriteToNode;

// Deny write of a specific type of value.
private OpcVariableValue<object> WriteToNode(
        OpcWriteVariableValueContext context,
        OpcVariableValue<object> value)
{
    if (!(value.Value is short))
        value.Status.Update(OpcStatusCode.BadTypeMismatch);

    return value;
}
```

```vbnet
Me.variableNode.WriteVariableValueCallback = AddressOf Me.WriteToNode

'Deny write of a specific type of value.
Private Function WriteToNode( _
        ByVal context As OpcWriteVariableValueContext, _
        ByVal value As OpcVariableValue(Of Object)) As OpcVariableValue(Of Object)
    If Not TypeOf value.Value Is Short Then
        value.Status.Update(OpcStatusCode.BadTypeMismatch)
    End If

    Return value
End Function
```

### Convert written values

Types used: `OpcVariableNode`, `OpcVariableValue`, `OpcWriteVariableValueContext`

A custom callback can convert the value to the desired target data type before it is written. The approach can look like this:

```csharp
// Inline change type of value to the expected type.
private OpcVariableValue<object> WriteToNode(
        OpcWriteVariableValueContext context,
        OpcVariableValue<object> value)
{
    if (!(value.Value is short)) {
        value = new OpcVariableValue<object>(
                Convert.ChangeType(value.Value, typeof(short)),
                value.Timestamp ?? DateTime.UtcNow,
                value.Status);
    }

    return value;
}
```

```vbnet
'Inline change type of value to the expected type.
Private Function WriteToNode( _
        ByVal context As OpcWriteVariableValueContext, _
        ByVal value As OpcVariableValue(Of Object)) As OpcVariableValue(Of Object)
    If Not TypeOf value.Value Is Short Then
        value = New OpcVariableValue(Of Object)( _
                Convert.ChangeType(value.Value, GetType(Short)), _
                If(value.Timestamp, DateTime.UtcNow), _
                value.Status)
    End If

    Return value
End Function

```

## Security

### DCOM hardening (CVE-2021-26414)

The SDK implements the measures Microsoft recommends for **OPC Classic**, that is, for communication over DCOM (Distributed Component Object Model) with OPC DA (Data Access), OPC HDA (Historical Data Access), and OPC AE (Alarms & Events).

**Background:**

Microsoft addresses the issue known as **CVE-2021-26414** with guidance for DCOM client and DCOM server applications: [CVE-2021-26414](https://msrc.microsoft.com/update-guide/vulnerability/CVE-2021-26414).

As part of this guidance, Microsoft describes the measures taken in phases that resolve the issue permanently. After the last phase took effect on June 14, 2022, communication problems between DCOM client and DCOM server applications can occur. This is expected to affect some of the applications that communicate over OPC Classic.

To keep communication over OPC Classic working after the measures for CVE-2021-26414 are complete, the SDK implements the **authentication levels** Microsoft recommends. If you do not specify one in `OpcClient.Security.AuthenticationLevel`, it uses `OpcClassicAuthenticationLevel.PacketIntegrity` (`RPC_C_AUTHN_LEVEL_PKT_INTEGRITY`).

The section [Remote servers and DCOM security](https://www.traeger.de/en/docs/development/opcua/client/opc-classic#remote-servers-and-dcom-security) describes how to set DCOM security in code.
