Softing.Opc.Ua.Client 3.2.0.6453

Prefix Reserved
There is a newer version of this package available.
See the version list below for details.
dotnet add package Softing.Opc.Ua.Client --version 3.2.0.6453                
NuGet\Install-Package Softing.Opc.Ua.Client -Version 3.2.0.6453                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Softing.Opc.Ua.Client" Version="3.2.0.6453" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add Softing.Opc.Ua.Client --version 3.2.0.6453                
#r "nuget: Softing.Opc.Ua.Client, 3.2.0.6453"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install Softing.Opc.Ua.Client as a Cake Addin
#addin nuget:?package=Softing.Opc.Ua.Client&version=3.2.0.6453

// Install Softing.Opc.Ua.Client as a Cake Tool
#tool nuget:?package=Softing.Opc.Ua.Client&version=3.2.0.6453                

Welcome

The OPC UA .NET Standard SDK enables fast integration of the OPC Unified Architecture standard communication interface within end user applications. It provides a comprehensive set of source code and binaries covering OPC UA functionality for building Client respective Server interface thus allowing short time to market of your solutions.

The SDK's libraries are accompanied with an easy to use API, allowing the user to concentrate on the own business requirements implementation of his product and hiding the OPC UA protocol complexity.

  • Leading edge easy-to-use client interface

  • Suitable for a wide range of applications

  • Higher portability

  • Validated by solid, well established battery of tests

The OPC UA .NET Standard SDK includes:

  • OPC UA .NET Standard SDK Client Library & OPC UA .NET Standard SDK Server Library - an easy to use API built on top of the OPC UA .NET Standard Stack from OPC Foundation.

  • Unchanged access to OPC UA .NET Standard Stack from OPC Foundation.

  • Support for custom complex data types decoding, compliant with OPC UA 1.03 specification.

What’s New

A list of the new features added in the released versions is provided below:

Version 3.20

  • Integrates the new version of OPC UA .NET Standard Stack from OPC Foundation (NuGet package OPCFoundation.NetStandard.Opc.Ua - version 1.4.369.30) which includes:

    • The fixes for the following cybersecurity vulnerabilities: CVE-2022-29862, CVE-2022-29863, CVE-2022-29864, CVE-2022-29865, CVE-2022-29866.
    • Improvements for Tracing and Logging described in The logging solution in Version 1.4.368.
  • Added support for Windows 11 and Windows Server 2022. Removed support for .NET Core 2.1 SDK.

  • Add support for Visual Studio 2022. HTTPS support of OPC UA .NET Standard Stack from OPC Foundation was tested and added as supported by the OPC UA .NET Standard SDK.

  • Sample code is available on github at https://github.com/SoftingIndustrial/Softing-OPC-UA-.Net-Standard-SDK-Samples

  • OPC UA .NET Standard SDK Server Library:

    • The PubSub node manager from SampleServer was disabled by default. It can be easily enabled by uncommenting a line of code.
    • More alarm types were added to the SampleServer.Added sample implementation of a custom role.
    • Bug fixes.
  • OPC UA .NET Standard SDK Client Library:

    • Added method UaApplication.CreateSessionCopy that will create a new session based on an existing connected session and it will also copy the custom data type information from the original object.
    • New feature added for reusing the custom data type information loaded into a ClientSession across the entire life of that ClientSession. See ReuseCustomDataTypeInfoAtReconnect for more details.
    • The connect logic from ClientSession was reworked. ConnectAsync and Connect methods will return only after the custom data types are loaded as configured by DecodeCustomDataTypes and DecodeDataTypeDictionaries flags from ClientToolkitConfiguration. There is no need to wait until the flags that indicate if custom data types are loaded become true.
    • The methods FetchDataDictionaries() and FetchDataTypeDefinitions() and all their variations were marked as obsolete since it is not recommended to be called from an already connected session while the session is receiving data change notifications.
    • Bug fixes.

Note: SDK libraries are compiled using NuGet package OPCFoundation.NetStandard.Opc.Ua - version 1.4.369.30. When using OPC UA .NET Standard SDK version 3.00 it is mandatory to use the same version of OPC UA .NET Standard Stack from OPC Foundation.

Server Development

OPC UA .NET Standard SDK Server Library provides an easy to use API for developing OPC UA server applications.

The servers you develop can be tested against a generic client application like OPC UA Client which is available here.

Programmer’s Guide

The subsequent documentation is assuming you are building your UA servers using the OPC UA .NET Standard SDK Server Library .

It will not enter in details about UA server development using the OPC UA .NET Standard Stack from OPC Foundation.

Getting Started

Your server development usually starts by creating a new project.

Next add reference to NuGet package Softing.Opc.Ua.Server.

You can proceed now to create an extension of UaServer Class.

UaServer Class

UaServer class available in OPC UA .NET Standard SDK Server Library provides all required functionality for implementing an OPC UA server out of the box with very little customization.

Define the Server

Create your server class (having for example the name MyServer) derived from Softing.Opc.Ua.Server.UaServer:

using Opc.Ua.Server;
namespace MySampleServer
{
    class MyServer : UaServer
    {
      public MyServer()
      {
          //set server manufacturer name property.
          ManufacturerName = "Your Company";
      }    
    }
}

Register Types

If your UA Server needs to instantiate different types of nodes that are not defined in the OPC UA .NET Standard Stack from OPC Foundation the UaServer class from OPC UA .NET Standard SDK Server Library provides a protected method RegisterTypes() that register all types derived from BaseVariableState or BaseObjectState into NodeStateFactory of the server. It can be called at server startup via OnServerStarted() method override.

/// <summary>
///  Called after the server has been started.
/// </summary>
/// <param name="server">The server.</param>
protected override void OnServerStarted(IServerInternal server)
{
  base.OnServerStarted(server);
  
  //register all types derived from BaseVariableState or BaseObjectState into NodeStateFactory of the server
  RegisterTypes(Assembly.LoadFile("CustomAssembly.dll"));
}

User Authentication

There are four user UserIdentityToken implementations in OPC UA .NET Standard Stack from OPC Foundation: AnonymousIdentityToken, UserNameIdentityToken, X509IdentityToken and IssuedIdentityToken.

The OPC UA .NET Standard SDK Server Library provides support for handling user authentication in implemented in UaServer class:

  • Anonymous identity token type has granted access if added to ServerConfiguration.UserTokenPolicies collection.

  • UserNameIdentityToken if added to ServerConfiguration.UserTokenPolicies collection: protected virtual bool ValidateUserPassword(string userName, string password)

ValidateUserPassword method allows derived classes to provide username and password authentication. Default implementation from UaServer returns true.

Please override this method and add your own validation.

protected virtual bool ValidateSystemConfigurationIdentity(string userName, string password)

ValidateSystemConfigurationIdentity method allows derived classes to provide username and password authentication for instantiating a SystemConfigurationIdentity. Default implementation from UaServer returns false.

Please override this method and add your own validation for users with SystemConfigurationIdentity access right.

  • X509IdentityToken if added to ServerConfiguration.UserTokenPolicies collection: protected virtual bool ValidateUserCertificate(X509Certificate2 userCertificate)

ValidateUserCertificate method allows derived classes to provide user certificate validation.

Default implementation of ValidateUserCertificate returns true if the userCertificate exists in ApplicationConfiguration.SecurityConfiguration.TrustedUserCertificates, is not self signed and its Subject Alternative Name does not start with 'urn'.

For a detailed explanation of configuration for X509 identity token validation please see Application Configuration section.

Please override this method and add your own validation if needed.

  • IssuedIdentityToken if added to ServerConfiguration.UserTokenPolicies collection: protected virtual bool ValidateIssuedIdentity(IssuedIdentityToken issuedIdentityToken)

Authenticates a user identified by WS-Security XML token. Default implementation of ValidateIssuedIdentity returns true.

Please override this method and add your own validation.

For details of how to specify supported user token policies please read Server onfiguration.

Role Identities Validation

If the server supports the User Authorization Information Model from OPC UA specification it shall override the criteria validation methods provided by UaServer.

The server will use these methods whenever an Identity is added to a Role by calling the AddIdentity method of a well known RoleState node from the RoleSet node from address space.

The same validation methods are used when calling the AddIdentityToRoleState() method from RoleStateHelper Class.

  • ValidateRoleUserNameCriteria method override shall check if the provided userName is valid and can be associated with the Role that has the specified roleId. Default implementation returns StatusCodes.Good.

protected virtual ServiceResult ValidateRoleUserNameCriteria(NodeId roleId, string userName)

  • ValidateThumbprintCriteria method override shall check if the provided certificate thumbprint is valid and can be associated with the Role that has the provided roleId. Default implementation returns StatusCodes.Good.

protected virtual ServiceResult ValidateThumbprintCriteria(NodeId roleId, string thumbprint)

  • ValidateRoleRestrictionNameCriteria method override shall check if the provided restrictionName is valid and can be associated with the Role that has the provided roleId. Default implementation returns StatusCodes.Good.

protected virtual ServiceResult ValidateRoleRestrictionNameCriteria(NodeId roleId, string restrictionName)

  • ValidateRoleGroupIdCriteria method override shall check if the provided groupId is valid and can be associated with the Role that has the provided roleId. Default implementation returns StatusCodes.Good.

protected virtual ServiceResult ValidateRoleGroupIdCriteria(NodeId roleId, string groupId)

  • ValidateRoleAnonymousCriteria method override shall check if anonymous user identity is valid and can be associated with the Role that has the provided roleId. Default implementation returns StatusCodes.Good only if the provided roleId is equal to ObjectIds.WellKnownRole_Anonymous.

protected virtual ServiceResult ValidateRoleAnonymousCriteria(NodeId roleId)

  • ValidateRoleAuthenticatedUserCriteria method override shall check if any authenticated user identity is valid and can be associated with the Role that has the provided roleId. Default implementation returns StatusCodes.Good only if the provided roleId is NOT equal to ObjectIds.WellKnownRole_Anonymous.

protected virtual ServiceResult ValidateRoleAuthenticatedUserCriteria(NodeId roleId)

Initialize NodeManagers Collection

UaServer implementation does not provide any default node manager.

To add custom node managers to the server you need to override CreateMasterNodeManager() method.

SampleServer Class describes how to override this method.

Note: If the server supports the User Authorization Information Model from OPC UA specification it shall add an instance of the RolesNodeManager Class to the list of server's node managers.

Customize the Server's Role Set

If the RolesNodeManager Class was instantiated and added to the list of Server's node managers the server will execute the OnRoleSetInitialized method.

Override this method and add here server specific role settings. By default the Anonymous identity is added to the ObjectIds.WellKnownRole_Anonymous role and the AuthenticatedUser identity is added to ObjectIds.WellKnownRole_AuthenticatedUser Role.

In SampleServer the OnRoleSetInitialized method override is like in the following code snippet:

/// <summary>
/// Custom implementation of RoleSet. Define custom role set
/// </summary>
/// <param name="server">The server.</param>
/// <param name="roleStateHelper">The helper class that implements roleSet methods</param>
public override void OnRoleSetInitialized(IServerInternal server, IRoleStateHelper roleStateHelper)
{
	// add username identity mapping to engineer role
	roleStateHelper.AddIdentityToRoleState(ObjectIds.WellKnownRole\_Engineer,
		new IdentityMappingRuleType {
		  CriteriaType = IdentityCriteriaType.UserName,
		  Criteria = EngineerUser
		  });

	// add username identity mapping to operator role
	roleStateHelper.AddIdentityToRoleState(ObjectIds.WellKnownRole\_Operator,
	new IdentityMappingRuleType {
	  CriteriaType = IdentityCriteriaType.UserName,
	  Criteria = OperatorUser1
	});

	// add username identity mapping to operator role
	roleStateHelper.AddIdentityToRoleState(ObjectIds.WellKnownRole\_Operator,
	new IdentityMappingRuleType {
	  CriteriaType = IdentityCriteriaType.UserName,
	  Criteria = OperatorUser2
	});

	// configure operator role to include all applicationUris
	roleStateHelper.AddApplicationToRoleState(ObjectIds.WellKnownRole\_Operator, "");
	roleStateHelper.SetExcludeApplications(ObjectIds.WellKnownRole\_Operator, true);
	base.OnRoleSetInitialized(server, roleStateHelper);
}

Start the Server

The UaServer class has four asynchronous Start() methods:

  • public async Task Start(string configFilePath)

This method will load the ApplicationConfiguration object from specified file path and start the server using that configuration object.

Sample of usage:

static void Main(string[] args)
{
  string configurationFile = "MySampleServer.Config.xml";
  MyServer server = new MyServer();
  server.Start(configurationFile).Wait();

  Console.WriteLine("MyServer started. press any key to stop");
  Console.ReadKey();
}
  • public new async Task Start(ApplicationConfiguration applicationConfiguration)

    This method will start the server using the specified configuration object.

  • public async Task Start(uint portNumber, ServerSecurityPolicyCollection securityPolicies = null, UserTokenPolicyCollection userTokenPolicies = null)

    This method will create a new ApplicationConfiguration object, assign the specified port number, assign specified security and user token policies.

If securityPolicies parameter is null or omitted, the server will be started with a default list of security policies: None, Sign and SignAndEncrypt.

If userTokenPolicies parameter is null or omitted, the server will be started with a default list of user token policies: Anonymous, UserName and Certificate.

  • public async Task Start(ApplicationConfigurationBuilderEx applicationConfigurationBuilder)

    This method will start the server using the specified fluent API configuration object.

Reverse Connect

The UaServer class provides three methods for managing reverse connectivity: GetReverseConnections, AddReverseConnection and RemoveReverseConnection:

  • public virtual Dictionary<Uri, ReverseConnectDetails> GetReverseConnections()

    The method returns the list of ReverseConnectDetails class instances registered with this UaServer. The reverse connections can be registered using the AddReverseConnection method or they can be defined in application configuration file as described here.

Usage sample:

var reverseConnections = sampleServer.GetReverseConnections();
if (reverseConnections?.Count > 0)
{
  // print configured reverse connections
  Console.WriteLine("Configured Reverse Connections:");
  foreach (var connection in reverseConnections)
  {
    Console.WriteLine(connection.Key);
  }
}
  • public virtual void AddReverseConnection(Uri clientUrl, int timeout = 0, int maxSessionCount = 0, bool enabled = true)

    Manually add a new connection based on client reverse connect url address.

The timeout represents the number of milliseconds to wait for establishing a reverse connection.

The maxSessionCount represents one of the following options:

  • the maximum number of active reverse connections

  • 0 means unlimited number of connections

The enabled flag states if the added reverse connection is enabled when added. It can be enabled/disabled at a later time.

  • public virtual bool RemoveReverseConnection(Uri clientUrl)

    Remove manually a reverse client connection from server's list.

Configuration Property

After Start() method call, the server object sets its Configuration read-only property with the ApplicationConfiguration object used to configure it.

Multiple server instances validation

  • public List<string> GetActiveListenersUris()

After Start() method call, also the method GetActiveListenersUris() is called getting active listeners list in order to verify if a server was already started on different process.

Only first instance of server could be active.

Check SampleServer.cs from SampleServer project SampleServer class for a sample customization of the UaServer class.

NodeManager Class

NodeManager class available in OPC UA .NET Standard SDK Server Library should be used as base class for all node managers implemented using OPC UA .NET Standard SDK.

It provides helper functions for creating different node instances like Variables and Objects. All helper functions available in NodeManager are described in Create Static Address Space section.

The SampleServer application provides a set of implementations of NodeManager described in: Alarms Node Manager, DataAccess Node Manager, HistoricalDataAccess Node Manager, Methods Node Manager, NodeSetImport Node Manager, Reference Node Manager, UserAuthentication Node Manager.

RolesNodeManager Class

The RolesNodeManager class is a ready to use node manager specialized in Roles management. It was added to OPC UA .NET Standard SDK Server Library in version 2.50.

Roles management can be added to any OPC UA Server implemented using the OPC UA .NET Standard SDK Server Library in three simple steps described in User Authorization for SampleServer.

Each server that has the RolesNodeManager added to it will also have the implementation of the following methods:

  • RoleSetState: AddRole and RemoveRole methods are implemented but return BadNotSupported status code.

  • RoleState: AddIdentity, RemoveIdentity, AddApplication, RemoveApplication, AddEndpoint, RemoveEndpoint methods are implemented for each of the well known roles (Anonymous, AuthenticatedUser, ConfigureAdmin, Engineer, Observer, Operator, SecurityAdmin and Supervisor).

The RolesNodeManager class uses RoleStateHelper Class when handling RoleState methods calls ensuring that the method calls from server's code are handled in the same way like the OPC UA method calls.

Note: The AddIdentitity method validates the identity mappings to be added and the validation is fully customizable by overriding some methods from UaServer class (see Role Identities Validation).

RoleStateHelper Class

RoleStateHelper class provides the actual logic for add/remove identity, add/remove endpoint and add/remove application to/from a RoleState.

RoleStateHelper class has the following methods:

  • AddIdentityToRoleState method adds the specified IdentityMappingRuleType instance to the RoleState specified by roleId parameter. The identity mapping rule is validated by the overrides implemented in your Server class (see UaServer Class). The method will return StatuCodes.Good if the identity add successfully added. public ServiceResult AddIdentityToRoleState(NodeId roleId, IdentityMappingRuleType rule)

Note: If the Identities property of a RoleState is null or an empty array, then the Role cannot be granted to any Session.

  • RemoveIdentityFromRoleState method removes the specified IdentityMappingRuleType instance from the RoleState specified by roleId parameter. The method will return StatuCodes.Good if the identity was successfully removed. public ServiceResult RemoveIdentityFromRoleState(NodeId roleId, IdentityMappingRuleType rule)

  • AddApplicationToRoleState method adds the specified applicationUri to the RoleState specified by roleId parameter. The method will return StatuCodes.Good if the applicationUri was successfully added. public ServiceResult AddApplicationToRoleState(NodeId roleId, string applicationUri)

  • RemoveApplicationFromRoleState method removes the specified applicationUri from the RoleState specified by roleId parameter. The method will return StatuCodes.Good if the applicationUri was successfully removed. public ServiceResult RemoveApplicationFromRoleState(NodeId roleId, string applicationUri)

  • SetExcludeApplications method sets the ExcludeApplications flag for the RoleState specified by roleId parameter. The method will return StatuCodes.Good if the flag was set without error. public ServiceResult SetExcludeApplications(NodeId roleId, bool exclude)

  • AddEndpointToRoleState method adds the specified endpoint to the RoleState specified by roleId parameter. The method will return StatuCodes.Good if the endpoint was successfully added. public ServiceResult AddEndpointToRoleState(NodeId roleId, EndpointType endpoint)

  • RemoveEndpointFromRoleState method removes the specified endpoint from the RoleState specified by roleId parameter. The method will return StatuCodes.Good if the endpoint was successfully removed. public ServiceResult RemoveEndpointFromRoleState(NodeId roleId, EndpointType endpoint)

  • SetExcludeEndpoints method sets the ExcludeEndpoints flag for the RoleState specified by roleId parameter. The method will return StatuCodes.Good if the flag was set without error. public ServiceResult SetExcludeEndpoints(NodeId roleId, bool exclude)

ReverseConnectDetails class

The ReverseConnectDetails class describes a reverse connection managed by the UaServer Class.

It has the following properties:

  • ClientUrl - The Url address of the reverse connect client.

  • Timeout - The timeout to wait for a response to a reverse connection.

  • MaxSessionCount - The maximum count of active reverse connect sessions. 0 means unlimited number of sessions.

  • Enabled - Specifies whether the sending of reverse connect attempts is enabled.

  • IsConfigurationEntry - Specifies whether the reverse connection is defined in application configuration.

  • ServiceResult - Indicates the last service status of the connection channel.

  • LastState - Indicate that the last connection state. Possible values are: Closed, Connecting, Connected, Rejected and Error.

  • RejectTime - The last timestamp when the connection was rejected by the client.

The Reverse Connect section describes how to register a reverse connection with current server.

Server Configuration

Using OPC UA .NET Standard SDK Server Library it is possible to specify configuration statically, programmatically using the ApplicationConfiguration and programmatically

using a fluent API.

Static configuration

The static configuration assumes the existence of a configuration file.

The configuration file approach has the advantage that it can be changed and when the server application is restarted the new values are used without the need to recompile code.

…

// Assume the configuration file is in the same folder with the executable
private const string ConfigurationFilePath = "SampleServer.config.xml";

…

// Start server using config file
await server.Start(configurationFile)

You can start your server configuration by adding an xml file to the project and copy the content of SampleServer.Config.xml from SampleServer application, then change it to suit your project's needs.

There is a sample of server configuration file adapted from SampleServer application:

<?xml version="1.0" encoding="utf-8"?>
<ApplicationConfiguration
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:ua="http://opcfoundation.org/UA/2008/02/Types.xsd"
  xmlns="http://opcfoundation.org/UA/SDK/Configuration.xsd">
  <ApplicationName>Softing NET Standard Sample Server</ApplicationName>
  <ApplicationUri>urn:localhost:Softing:UANETStandardToolkit:SampleServer</ApplicationUri>
  <ProductUri>http://industrial.softing.com/OpcUaNetStandardToolkit/SampleServer</ProductUri>
  <ApplicationType>Server\_0</ApplicationType>
  <SecurityConfiguration>

  
  <ApplicationCertificate>
  <StoreType>Directory</StoreType>
  <StorePath>%CommonApplicationData%/Softing/OpcUaNetStandardToolkit/pki/own</StorePath>
  <SubjectName>SoftingOpcUaSampleServer</SubjectName>
  </ApplicationCertificate>

  
  <TrustedIssuerCertificates>
    <StoreType>Directory</StoreType>
    <StorePath>%CommonApplicationData%/Softing/OpcUaNetStandardToolkit/pki/issuer</StorePath>
  </TrustedIssuerCertificates>

  
  <TrustedPeerCertificates>
    <StoreType>Directory</StoreType>
    <StorePath>%CommonApplicationData%/Softing/OpcUaNetStandardToolkit/pki/trusted</StorePath>
  </TrustedPeerCertificates>

  
  <NonceLength>32</NonceLength>

  
  <RejectedCertificateStore>
    <StoreType>Directory</StoreType>
    <StorePath>%CommonApplicationData%/Softing/OpcUaNetStandardToolkit/pki/rejected</StorePath>
    </RejectedCertificateStore>
    
    <AutoAcceptUntrustedCertificates>false</AutoAcceptUntrustedCertificates>
    
    <UserRoleDirectory>%CommonApplicationData%/Softing/OpcUaNetStandardToolkit/userRoles</UserRoleDirectory>
    
    <RejectSHA1SignedCertificates>false</RejectSHA1SignedCertificates>
    
    <MinimumCertificateKeySize>1024</MinimumCertificateKeySize>
    
    <AddAppCertToTrustedStore>true</AddAppCertToTrustedStore>
    
    <SendCertificateChain>false</SendCertificateChain>
    
    <UserIssuerCertificates>
      <StoreType>Directory</StoreType>
      <StorePath>%CommonApplicationData%/Softing/OpcUaNetStandardToolkit/pki/issuerUser</StorePath>
   </UserIssuerCertificates>

 
 <TrustedUserCertificates>
    <StoreType>Directory</StoreType>
    <StorePath>%CommonApplicationData%/Softing/OpcUaNetStandardToolkit/pki/trustedUser</StorePath>
 </TrustedUserCertificates>    
 </SecurityConfiguration>
 <TransportConfigurations></TransportConfigurations>
 <TransportQuotas>
 <OperationTimeout>600000</OperationTimeout>
 <MaxStringLength>1048576</MaxStringLength>
 <MaxByteStringLength>1048576</MaxByteStringLength>
 <MaxArrayLength>65535</MaxArrayLength>
 <MaxMessageSize>4194304</MaxMessageSize>
 <MaxBufferSize>65535</MaxBufferSize>
 <ChannelLifetime>300000</ChannelLifetime>
 <SecurityTokenLifetime>3600000</SecurityTokenLifetime>
 </TransportQuotas>
 <ServerConfiguration>
  <BaseAddresses>
    <ua:String>opc.tcp://localhost:61510/SampleServer</ua:String>
  </BaseAddresses>
  <SecurityPolicies>
     <ServerSecurityPolicy>
      <SecurityMode>None\_1</SecurityMode>
      <SecurityPolicyUri>http://opcfoundation.org/UA/SecurityPolicy#None</SecurityPolicyUri>
     </ServerSecurityPolicy>
     <ServerSecurityPolicy>
      <SecurityMode>Sign\_2</SecurityMode>
      <SecurityPolicyUri>http://opcfoundation.org/UA/SecurityPolicy#Basic256Sha256</SecurityPolicyUri>
     </ServerSecurityPolicy>
     <ServerSecurityPolicy>
      <SecurityMode>SignAndEncrypt\_3</SecurityMode>
      <SecurityPolicyUri>http://opcfoundation.org/UA/SecurityPolicy#Basic256Sha256</SecurityPolicyUri>
     </ServerSecurityPolicy>
     <ServerSecurityPolicy>
      <SecurityMode>Sign\_2</SecurityMode>
      <SecurityPolicyUri>http://opcfoundation.org/UA/SecurityPolicy#Aes128\_Sha256\_RsaOaep</SecurityPolicyUri>
     </ServerSecurityPolicy>
     <ServerSecurityPolicy>
      <SecurityMode>SignAndEncrypt\_3</SecurityMode>
      <SecurityPolicyUri>http://opcfoundation.org/UA/SecurityPolicy#Aes128\_Sha256\_RsaOaep</SecurityPolicyUri>
     </ServerSecurityPolicy>
     <ServerSecurityPolicy>
      <SecurityMode>Sign\_2</SecurityMode>
      <SecurityPolicyUri>http://opcfoundation.org/UA/SecurityPolicy#Aes256\_Sha256\_RsaPss</SecurityPolicyUri>
      <SecurityLevel>2</SecurityLevel>
     </ServerSecurityPolicy>
     <ServerSecurityPolicy>
      <SecurityMode>SignAndEncrypt\_3</SecurityMode>
      <SecurityPolicyUri>http://opcfoundation.org/UA/SecurityPolicy#Aes256\_Sha256\_RsaPss</SecurityPolicyUri>
      <SecurityLevel>5</SecurityLevel>
     </ServerSecurityPolicy>
    </SecurityPolicies>
  <UserTokenPolicies>
    
    <ua:UserTokenPolicy>
     <ua:TokenType>Anonymous\_0</ua:TokenType>
     <ua:SecurityPolicyUri>http://opcfoundation.org/UA/SecurityPolicy#None</ua:SecurityPolicyUri>
    </ua:UserTokenPolicy>
    
    <ua:UserTokenPolicy>
     <ua:TokenType>UserName\_1</ua:TokenType>
     
     <ua:SecurityPolicyUri>http://opcfoundation.org/UA/SecurityPolicy#Basic256Sha256</ua:SecurityPolicyUri>
    </ua:UserTokenPolicy>
    
    <ua:UserTokenPolicy>
     <ua:TokenType>Certificate\_2</ua:TokenType>
     
     <ua:SecurityPolicyUri>http://opcfoundation.org/UA/SecurityPolicy#Basic256Sha256</ua:SecurityPolicyUri>
    </ua:UserTokenPolicy>
  </UserTokenPolicies>
  <DiagnosticsEnabled>true</DiagnosticsEnabled>
  <MaxSessionCount>100</MaxSessionCount>
  <MinSessionTimeout>10000</MinSessionTimeout>
  <MaxSessionTimeout>3600000</MaxSessionTimeout>
  <MaxBrowseContinuationPoints>10</MaxBrowseContinuationPoints>
  <MaxQueryContinuationPoints>10</MaxQueryContinuationPoints>
  <MaxHistoryContinuationPoints>100</MaxHistoryContinuationPoints>
  <MaxRequestAge>600000</MaxRequestAge>
  <MinPublishingInterval>100</MinPublishingInterval>
  <MaxPublishingInterval>3600000</MaxPublishingInterval>
  <PublishingResolution>50</PublishingResolution>
  <MaxSubscriptionLifetime>3600000</MaxSubscriptionLifetime>
  <MaxMessageQueueSize>100</MaxMessageQueueSize>
  <MaxNotificationQueueSize>100</MaxNotificationQueueSize>
  <MaxNotificationsPerPublish>1000</MaxNotificationsPerPublish>
  <MinMetadataSamplingInterval>1000</MinMetadataSamplingInterval>
  <AvailableSamplingRates>
    <SamplingRateGroup>
     <Start>5</Start>
     <Increment>5</Increment>
     <Count>20</Count>
    </SamplingRateGroup>
    <SamplingRateGroup>
     <Start>100</Start>
     <Increment>100</Increment>
     <Count>4</Count>
    </SamplingRateGroup>
    <SamplingRateGroup>
     <Start>500</Start>
     <Increment>250</Increment>
     <Count>2</Count>
    </SamplingRateGroup>
    <SamplingRateGroup>
     <Start>1000</Start>
     <Increment>500</Increment>
     <Count>20</Count>
    </SamplingRateGroup>
  </AvailableSamplingRates>
  <MaxRegistrationInterval>30000</MaxRegistrationInterval>
  <NodeManagerSaveFile>SampleServer.nodes.xml</NodeManagerSaveFile>
  <MinSubscriptionLifetime>10000</MinSubscriptionLifetime>
  <MaxPublishRequestCount>20</MaxPublishRequestCount>
  <MaxSubscriptionCount>100</MaxSubscriptionCount>
  <MaxEventQueueSize>10000</MaxEventQueueSize>
  <ServerProfileArray>
    <ua:String>Standard UA Server Profile</ua:String>
    <ua:String>Data Access Server Facet</ua:String>
    <ua:String>Method Server Facet</ua:String>
  </ServerProfileArray>
 </ServerConfiguration>
 <Extensions>  
  <ua:XmlElement>
    
  </ua:XmlElement>
 </Extensions>
 <TraceConfiguration>
  <OutputFilePath>%CommonApplicationData%/Softing/OpcUaNetStandardToolkit/logs/SampleServer.log</OutputFilePath>
  <DeleteOnLoad>true</DeleteOnLoad>
  
  <TraceMasks>519</TraceMasks>
 </TraceConfiguration>
 <DisableHiResClock>false</DisableHiResClock>
</ApplicationConfiguration>

Note: the order of xml elements from server configuration file is very important. If the order of elements is changed the configuration might be broken after loading from file. Below you have an example that provides the correct order:

Programmatic configuration using the ApplicationConfiguration

Even if it’s not the recommended way, because is limiting further configuration changes, the client configuration can be defined programmatically in your code like in the code bellow.

string serverName = "ServerName";
int portNumber = 4444;
configuration.ApplicationName = serverName;
configuration.ApplicationType = ApplicationType.Server;
configuration.ApplicationUri = $"urn:localhost:Softing:UANETStandardToolkit:{serverName}";
configuration.TransportConfigurations = new TransportConfigurationCollection();

configuration.TransportQuotas = new TransportQuotas { OperationTimeout = 15000 };
configuration.ServerConfiguration = new ServerConfiguration();
configuration.ServerConfiguration.BaseAddresses = new StringCollection() { $"opc.tcp://localhost:{portNumber}/{serverName}" };

// Set TraceConfiguration
configuration.TraceConfiguration = new TraceConfiguration()
{
    OutputFilePath = $@"%CommonApplicationData%\Softing\OpcUaNetStandardToolkit\logs\{serverName}.log",
    TraceMasks = 519
if (securityPolicies == null)
{
  configuration.ServerConfiguration.SecurityPolicies = new ServerSecurityPolicyCollection()
  {
     new ServerSecurityPolicy()
     {
        SecurityMode = MessageSecurityMode.None,
      SecurityPolicyUri = "http://opcfoundation.org/UA/SecurityPolicy#None",

     },

     new ServerSecurityPolicy()
     {

      SecurityMode = MessageSecurityMode.Sign,
      SecurityPolicyUri = "http://opcfoundation.org/UA/SecurityPolicy#Basic256Sha256",

     },

     new ServerSecurityPolicy()
     {

      SecurityMode = MessageSecurityMode.SignAndEncrypt,
      SecurityPolicyUri = "http://opcfoundation.org/UA/SecurityPolicy#Basic256Sha256",
     }
  };
}
else
{
    configuration.ServerConfiguration.SecurityPolicies = securityPolicies;
}

if (userTokenPolicies == null)
{
  configuration.ServerConfiguration.UserTokenPolicies = new UserTokenPolicyCollection()
  {
     new UserTokenPolicy()
     {
      TokenType = UserTokenType.Anonymous,
      SecurityPolicyUri = "http://opcfoundation.org/UA/SecurityPolicy#None",
     },

     new UserTokenPolicy()
     {
      TokenType = UserTokenType.UserName,
      SecurityPolicyUri = "http://opcfoundation.org/UA/SecurityPolicy#Basic256Sha256",
     },

     new UserTokenPolicy()
     {
      TokenType = UserTokenType.Certificate,
      SecurityPolicyUri = "http://opcfoundation.org/UA/SecurityPolicy#Basic256Sha256",
     }
  };
}
else
{
  configuration.ServerConfiguration.UserTokenPolicies = userTokenPolicies;
}

configuration.SecurityConfiguration = new SecurityConfiguration

  ApplicationCertificate = new CertificateIdentifier
  {
     StoreType = CertificateStoreType.Directory,
     StorePath = @"%CommonApplicationData%\Softing\OpcUaNetStandardToolkit\pki\own"
  },
  TrustedPeerCertificates = new CertificateTrustList
  {
     StoreType = CertificateStoreType.Directory,
     StorePath = @"%CommonApplicationData%\Softing\OpcUaNetStandardToolkit\pki\trusted",
  },
  TrustedIssuerCertificates = new CertificateTrustList
  {
     StoreType = CertificateStoreType.Directory,
     StorePath = @"%CommonApplicationData%\Softing\OpcUaNetStandardToolkit\pki\issuer",
  },
  RejectedCertificateStore = new CertificateTrustList
  {
     StoreType = CertificateStoreType.Directory,
     StorePath = @"%CommonApplicationData%\Softing\OpcUaNetStandardToolkit\pki\rejected",
  },
  UserIssuerCertificates = new CertificateTrustList
  {
     StoreType = CertificateStoreType.Directory,
     StorePath = @"%CommonApplicationData%\Softing\OpcUaNetStandardToolkit\pki\issuerUser",
  },
  TrustedUserCertificates = new CertificateTrustList
  {
     StoreType = CertificateStoreType.Directory,
     StorePath = @"%CommonApplicationData%\Softing\OpcUaNetStandardToolkit\pki\trustedUser",
  },
  AutoAcceptUntrustedCertificates = true
};

After the ApplicationConfiguration object is created it can be used to start the OPC UA Server derived from UaServer class.

Programmatic configuration using a fluent API

Besides using XML configuration files to configure instances of OPC UA servers the OPC UA .NET Standard SDK facilitates the creation of such configurations using a fluent configuration API.

The fluent configuration API can be accessed through an instance of the ApplicationConfigurationBuilderEx class, which, through a chained method call sequence, simplifies the parametrization of the configuration*.*

In case there are duplicate method names, one ending with the "Ext" prefix and the other not, the one ending with "Ext" prefix should be used instead!

OPC UA .NET Standard SDK introduces the following "Ext" prefixed extension method:

 public static IApplicationConfigurationBuilderSecurityOptions AddSecurityConfigurationExt(|
	   this IApplicationConfigurationBuilderSecurity appBuilderExtended,
	        string subjectName,
	        string pkiRoot = null,
	        string appRoot = null,
	        string rejectedRoot = null)

If the AddSecurityConfigurationExt method is used, than the default values for the folders relative to pkiRoot will be relative to:

"%CommonApplicationData%/Softing/OpcUaNetStandardToolkit/pki"

Additional to the methods added by the stack, OPC UA .NET Standard SDK adds the following methods:

  • public ApplicationConfigurationBuilderEx Initialize(string applicationUri,string productUri)

  • public ApplicationConfigurationBuilderEx SetApplicationName(string applicationName)

  • public ApplicationConfigurationBuilderEx DisableHiResClock(bool value)

and the following extension methods:

  • public static IApplicationConfigurationBuilderSecurityOptions SetUserRoleDirectory(this IApplicationConfigurationBuilderSecurityOptions appBuilderExtended, string userRoleDirectory)

  • public static IApplicationConfigurationBuilderSecurityOptions SetUserRoleDirectory(this IApplicationConfigurationBuilderSecurityOptions appBuilderExtended, string userRoleDirectory)

  • public static IApplicationConfigurationBuilderSecurityOptions SetNonceLength(this IApplicationConfigurationBuilderSecurityOptions appBuilderExtended, int nonceLength)

The names of the methods called to parametrize the configuration are intuitively chosen for each parameter name and need to be called in a specific order as the following example configuration shows.

The creation of a fluent configuration through an instance of the ApplicationConfigurationBuilderEx class starts with specifying the type of desired configuration:

ApplicationConfigurationBuilderEx applicationConfigurationBuilder = new ApplicationConfigurationBuilderEx(ApplicationType.Server);

Subsequently the chained method call sequence through which the parametrization of the configuration follows:

await applicationConfigurationBuilder.Initialize("urn:localhost:Softing:UANETStandardToolkit:SampleServer",                                                                                                            "http://industrial.softing.com/OpcUaNetStandardToolkit/SampleServer")
	.SetApplicationName("Softing NET Standard Sample Server")
	.DisableHiResClock(true)
	.SetTransportQuotas(new Opc.Ua.TransportQuotas()
	{
		OperationTimeout = 600000,
		MaxStringLength = 1048576,
        MaxByteStringLength = 1048576,
		MaxMessageSize = 4194304,
       ChannelLifetime = 300000,
	})

The server specific parametrization:

     .AsServer(new string[] { "opc.tcp://localhost:61510/SampleServer" })
		.AddUnsecurePolicyNone()
		.AddSignAndEncryptPolicies()
		.AddPolicy(Opc.Ua.MessageSecurityMode.Sign, "http://opcfoundation.org/UA/SecurityPolicy#Basic256Sha256")
		.AddPolicy(Opc.Ua.MessageSecurityMode.SignAndEncrypt, "http://opcfoundation.org/UA/SecurityPolicy#Basic256Sha256")
		.AddPolicy(Opc.Ua.MessageSecurityMode.Sign, "http://opcfoundation.org/UA/SecurityPolicy#Aes128\_Sha256\_RsaOaep")
		.AddPolicy(Opc.Ua.MessageSecurityMode.SignAndEncrypt, "http://opcfoundation.org/UA/SecurityPolicy#Aes128\_Sha256\_RsaOaep")
		.AddPolicy(Opc.Ua.MessageSecurityMode.Sign, "http://opcfoundation.org/UA/SecurityPolicy#Aes256\_Sha256\_RsaPss")
		.AddPolicy(Opc.Ua.MessageSecurityMode.SignAndEncrypt, "http://opcfoundation.org/UA/SecurityPolicy#Aes256\_Sha256\_RsaPss")
		.AddUserTokenPolicy(new Opc.Ua.UserTokenPolicy() { TokenType = Opc.Ua.UserTokenType.Anonymous, SecurityPolicyUri =                                                                             "http://opcfoundation.org/UA/SecurityPolicy#None" })
		.AddUserTokenPolicy(new Opc.Ua.UserTokenPolicy() { TokenType = Opc.Ua.UserTokenType.UserName, SecurityPolicyUri =                                                                             "http://opcfoundation.org/UA/SecurityPolicy#Basic256Sha256" })
		.AddUserTokenPolicy(new Opc.Ua.UserTokenPolicy() { TokenType = Opc.Ua.UserTokenType.Certificate, SecurityPolicyUri =                                                                     "http://opcfoundation.org/UA/SecurityPolicy#Basic256Sha256" })
		   .SetDiagnosticsEnabled(true)
		   .SetPublishingResolution(50)
		   .SetMaxMessageQueueSize(100)
		   .SetMaxNotificationsPerPublish(1000)
		   .SetAvailableSamplingRates(new Opc.Ua.SamplingRateGroupCollection() {
				new Opc.Ua.SamplingRateGroup(){Start=5, Increment=5, Count=20},
				new Opc.Ua.SamplingRateGroup(){Start=100, Increment=100, Count=4},
				new Opc.Ua.SamplingRateGroup(){Start=500, Increment=250, Count=2},
				new Opc.Ua.SamplingRateGroup(){Start=100, Increment=500, Count=20},
		   })
		   .SetNodeManagerSaveFile("SampleServer.nodes.xml")
		   .SetMaxPublishRequestCount(100)
		   .SetMaxSubscriptionCount(200)
		   .AddServerProfile("http://opcfoundation.org/UA-Profile/Server/StandardUA2017")
		   .AddServerProfile("http://opcfoundation.org/UA-Profile/Server/DataAccess")
		   .AddServerProfile("http://opcfoundation.org/UA-Profile/Server/Methods")
		   .AddServerProfile("http://opcfoundation.org/UA-Profile/Server/ReverseConnect")
		   .SetReverseConnect(new Opc.Ua.ReverseConnectServerConfiguration()
		   {
			   Clients = new Opc.Ua.ReverseConnectClientCollection()
			   {
				new Opc.Ua.ReverseConnectClient()
					   { EndpointUrl="opc.tcp://localhost:61512", Timeout=30000, MaxSessionCount=0, Enabled=true}
			   },
			   ConnectInterval = 10000,
			   RejectTimeout = 20000
		   })

Followed by Security specific configuration:

   .AddSecurityConfigurationExt(
	  "SoftingOpcUaSampleServer",
	  "%CommonApplicationData%/Softing/OpcUaNetStandardToolkit/pki",
	  "%CommonApplicationData%/Softing/OpcUaNetStandardToolkit/pki",
	  "%CommonApplicationData%/Softing/OpcUaNetStandardToolkit/pki")
	   .SetRejectSHA1SignedCertificates(false)
	   .SetUserRoleDirectory("%CommonApplicationData%/Softing/OpcUaNetStandardToolkit/userRoles")

Then the application domain specific configuration is added using the generic AddExtension<TConfiguration>:

   .AddExtension<SampleServerConfiguration>(new XmlQualifiedName("SampleServerConfiguration"),
	  new SampleServerConfiguration() { TimerInterval = 1000, ClearCachedCertificatesInterval = 30000 })

The logging specific parametrization calls:

	.SetTraceMasks(1)
	.SetOutputFilePath("%CommonApplicationData%/Softing/OpcUaNetStandardToolkit/logs/SampleServer.log")
	.SetDeleteOnLoad(true)

Finally the chained methods conclude with the asynchronous Create() method call which concludes the creation of the configuration.

           .Create();

After the ApplicationConfigurationBuilderEx object is created it can be used to start the OPC UA Server derived from UaServer class.

Application Configuration

Application Configuration

The server provides an extensible mechanism for storing the application configuration in an XML file. The class is extensible so developers can add their own configuration information to it. The following table describes primary elements of the ApplicationConfiguration class.

ApplicationConfiguration Properties:

Name Type Description
ApplicationName String <p>A human readable name for the application. </p><p>This setting is mandatory.</p>
ApplicationUri String <p>A globally unique name for the application. This should be a URL with which the machine domain name or IP address as the hostname followed by the vendor/product name followed by an instance identifier. </p><p>For example: http://machine1/OPC/UADemoServer</p><p>Default value is "urn:HOST_NAME:ApplicationName".</p>
ProductUri String <p>A human readable name for the product. </p><p>Default value is null.</p>
ApplicationType ApplicationType <p>The type of application. Possible values: Server, Client, ClientAndServer and DiscoveryServer.</p><p>Default value is ApplicationType.Server.</p>
SecurityConfiguration SecurityConfiguration <p>The security configuration for the application. Specifies the application instance certificate, list of trusted peers and trusted certificate authorities. </p><p>Default value is new SecurityConfiguration with default values. </p>
TransportConfigurations TransportConfiguration Collection <p>Specifies the Bindings to used for each transport protocol used by the application. </p><p>Default value is empty TransportConfiguration Collection.</p>
TransportQuotas TransportQuotas <p>Specifies the default limits to use when initializing WCF channels and endpoints. </p><p>Default value is null but is advisable to define this setting.</p>
ServerConfiguration ServerConfiguration <p>Specifies the configuration for Servers.</p><p>This setting is mandatory if ApplicationType is Server.</p><p>Default value is null.</p>
DiscoveryServerConfiguration DiscoveryServer Configuration <p>Specifies the configuration for Discovery Servers.</p><p>This setting is mandatory if ApplicationType is DiscoveryServer.</p><p>Default value is null.</p>
Extensions XmlElementCollection <p>Allows developers to add additional information to the file. </p><p>Default value is null.</p>
TraceConfiguration TraceConfiguration <p>Specifies the location of the Trace file. </p><p>Unexpected exceptions that are silently handled are written to the trace file. Developers can add their own trace output with the Utils.Trace(…) functions. </p><p>Default value is new instance of TraceConfiguration.</p>

The ApplicationConfiguration can be persisted anywhere but the class provides functions that load/save the configuration as an XML file on disk. The location of the XML file can be specified in the app.config file for the application if the ConfigurationLocation is specified as a configuration section.

Below is declared the configuration section MyApplicationName.

<configSections>
  <section name="MySampleServer" type="ApplicationConfigurationSection"/>
</configSections>

The configuration section name may be any text that is unique within the app.config file. The ConfigurationLocation would look like this XML fragment:

<MySampleServer>

  <ConfigurationLocation xmlns="http://opcfoundation.org/UA/SDK/Configuration.xsd">
	 <FilePath>MySampleServer.Config.xml</FilePath>
  </ConfigurationLocation>

</MySampleServer>

The FilePath can be an absolute path or a relative path.

If it is a relative path, the current directory is searched followed by the directory where the executable resides. The stack also supports prefixes which can be replaced with environment variables. The latter functionality requires a token enclosed by ‘%’ symbols at the start of the message. The stack will first check for a symbol that matches one of the values from the Environment.SpecialFolder enumeration. If not found it will use the environment variable of the same name.

Note that the same feature exists for all fields that represent file directory paths in the ApplicationConfiguration object. Developers can use this feature in their own code with the Utils.GetAbsoluteFilePath method.

SecurityConfiguration

This XML element contains the server certificate settings:

ApplicationCertificate

Defines the location of the application instance certificate in the Windows certificate store, and contains the following options:

  • <StoreType></StoreType>: The type of store: Directory.

  • <StorePath></StorePath>: The location of the store.

  • <SubjectName></SubjectName>: The subject for the certificate.

TrustedIssuerCertificates

Defines the list of certificate authorities trusted by the current server instance:

  • <StoreType></StoreType>: The type of store: Directory.

  • <StorePath></StorePath>: The location of the store.

TrustedPeerCertificats

Defines the list of trusted certificates:

  • <StoreType></StoreType>: The type of store: Directory.

  • <StorePath></StorePath>: The location of the store.

NonceLength

The length of nonce in the CreateSession service.

RejectedCertificates

Defines the list of rejected certificates:

  • <StoreType></StoreType>: The type of store: Directory.

  • <StorePath></StorePath>: The location of the store.

AutoAcceptUntrustedCertificates

This flag can be set by servers that allow untrusted certificates to be automatically accepted.

The flag is ignored in case the application subscribes to CertificateValidator.CertificateValidation event.

UserRoleDirectory

Directory which contains files representing users roles.

RejectSHA1SignedCertificates

This flag indicates if SHA1 signed certificates are rejected.

MinimumCertificateKeySize

This setting indicates the minimum accepted certificate key strength.

AddAppCertToTrustedStore

This flag indicates whether the application certificate should be copied to the trusted store.

SendCertificateChain

This flag indicates whether the application should send the complete certificate chain.

UserIssuerCertificates

Defines the list of user issuer certificates trusted by the current server instance:

  • <StoreType></StoreType>: The type of store: Windows or Directory.

  • <StorePath></StorePath>: The location of the store.

TrustedUserCertificates

Defines the list of trusted user certificates:

  • <StoreType></StoreType>: The type of store: Windows or Directory.

  • <StorePath></StorePath>: The location of the store. Note: More details about certificate validation settings are available in Certificate Validation and the Stack section.

TransportQuotas

Specifies quotas used by the transport layer. The following options can be set:

Name Type Description
OperationTimeout Int32 <p>The number of milliseconds to wait for a response from the Server. This value is only used by Clients. </p><p>Default value is 120000.</p>
MaxStringLength Int32 <p>The maximum length in characters of a single string contained in a message. </p><p>Default value is 65535.</p>
MaxByteStringLength Int32 <p>The maximum length in bytes of a single byte string contained in a message. This parameter is only completely supported when the OPC UA Binary Encoding is used. If UA XML encoding is used it should be the same as the MaxArrayLength. </p><p>Default value is 65535.</p>
MaxArrayLength Int32 <p>The maximum length of a single array contained in a message. </p><p>Default value is 65535.</p>
MaxMessageSize Int32 <p>The maximum length in bytes for a single message. </p><p>Default value is 1048576.</p>
MaxBufferSize Int32 <p>The maximum buffer size to use when sending or receiving. Note that the WCF stack will allocate memory blocks of this size so setting this parameter to large values will consume memory and reduce performance. </p><p>Default value is 65535.</p>
ChannelLifetime Int32 <p>The number of milliseconds before a server releases the resources for a broken secure channel. This parameter is only used with the OPC UA TCP transport. </p><p>Default value is 600000.</p>
SecurityTokenLifetime Int32 <p>The number of milliseconds before a security token must be renewed. </p><p>Default value is 3600000.</p>

ServerConfiguration

The following options are available for configuring the server:

Name Type Description
BaseAddresses StringCollection <p>Defines the set of addresses. One address is defined for each supported protocol.</p><p>Default value is new empty StringCollection.</p>
AlternateBaseAddresses StringCollection <p>Defines the list of alternate addresses that can be used to communicate with the server.</p><p>Default value is new empty StringCollection.</p>
SecurityPolicies ServerSecurityPolicyCollection <p>The security policies supported by the server.</p><p>Default value is a ServerSecurityPolicyCollection with one </p><p>ServerSecurityPolicy </p><p>· SecurityMode = MessageSecurityMode.SignAndEncrypt;</p>· SecurityPolicyUri = SecurityPolicies.Basic256Sha256;</p>
MinRequestThreadCount Int32 <p>The minimum number of threads assigned to processing requests.</p><p>Default value is 10.</p>
MaxRequestThreadCount Int32 <p>The maximum number of threads assigned to processing requests.</p><p>Default value is 100.</p>
MaxQueuedRequestCount Int32 <p>The maximum number of requests to queue before additional requests are rejected with BadTooManyOperations.</p><p>Default value is 200.</p>
UserTokenPolicies UserTokenPolicyCollection <p>Specifies any of the following identities: Anonymous, Username/Password, X509v3 Certificate, and WS-SecurityToken.</p><p>Default value is a UserTokenPolicyCollection with one </p><p>UserTokenPolicy </p><p>· TokenType = UserTokenType.Anonymous;</p>
DiagnosticsEnabled Boolean <p>Specifies whether the server updates its diagnostic information.</p><p>Default value is false.</p>
MaxSessionCount Int32 <p>The maximum number of simultaneous sessions.</p><p>If it is set on 0 there are no limitations for the number of possible sessions.</p><p>Default value is 100.</p>
MinSessionTimeout Int32 <p>The minimum number of milliseconds that a Session remains open without activity.</p><p>Default value is 10000.</p>
MaxSessionTimeout Int32 <p>The maximum number of milliseconds that a Session remains open without activity.</p><p>Default value is 3600000.</p>
MaxBrowseContinuationPoints Int32 <p>The maximum number of browse continuation points per session.</p><p>Default value is 10.</p>
MaxQueryContinuationPoints Int32 <p>The maximum number of query continuation points per session.</p><p>Default value is 10.</p>
MaxHistoryContinuationPoints Int32 <p>The maximum number of history continuation points per session.</p><p>Default value is 100.</p>
MaxRequestAge Int32 <p>The maximum age of an incoming request. Old requests are rejected.</p><p>Default value is 600000.</p>
MinPublishingInterval Int32 <p>The minimum allowed publishing cyclic rate (in milliseconds).</p><p>Default value is 100.</p>
MaxPublishingInterval Int32 <p>The maximum allowed publishing cyclic rate (in milliseconds).</p><p>Default value is 3600000.</p>
PublishingResolution Int32 <p>Specifies the fastest publishing rate for the Server.</p><p>Default value is 100.</p>
MaxSubscriptionLifetime Int32 <p>If the publishing timer has expired this number of times without a Publish request, then the Subscription is deleted.</p><p>Default value is 3600000.</p>
MaxMessageQueueSize Int32 <p>The maximum number of sent messages kept in the queue for each subscription.</p><p>Default value is 10.</p>
MaxNotificationQueueSize Int32 <p>The maximum number of notifications kept in the queue for each data change monitored item.</p><p>Default value is 100.</p>
MaxNotificationsPerPublish Int32 <p>The maximum numbers of messages sent in a single publish response.</p><p>Default value is 100.</p>
MinMetadataSamplingInterval Int32 <p>The minimum sampling rate for rarely changing metadata information.</p><p>Default value is 1000.</p>
AvailableSamplingRates SamplingRateGroupCollection <p>The available sampling rates.</p><p>Default value is new instance of SamplingRateGroupCollection*.*</p>
RegistrationEndpoint EndpointDescription <p>Specifies the connection information with the DiscoveryServer.</p><p>Default value is null.</p>
MaxRegistrationInterval Int32 <p>The maximum interval between registrations. 0 disables periodic registration.</p><p>Default value is 30000.</p>
NodeManagerSaveFile String <p>The file used to save nodes added to the CoreNodeManager.</p><p>Default value is null.</p>
MinSubscriptionLifetime Int32 <p>The minimum subscription lifetime.</p><p>Default value is 10000.</p>
MaxPublishRequestCount Int32 <p>The max publish request count.</p><p>Default value is 20.</p>
MaxSubscriptionCount Int32 <p>The maximum number of subscriptions that can be created in server at a certain moment.</p><p>Default value is 100.</p>
MaxEventQueueSize Int32 <p>The maximum number of notifications kept in the queue for each event monitored item.</p><p>Default value is 10000.</p>
ServerProfileArray StringCollection <p>The server profile array.</p><p>Default value is {"http://opcfoundation.org/UA-Profile/Server/StandardUA2017"}</p>
ShutdownDelay Int32 <p>The number of seconds that the server will wait before shut down.</p><p>Default value is 5.</p>
ServerCapabilities StringCollection <p>The server capabilities. </p><p>This value will be used to populate the ServerConfiguration node.</p><p>Default value is {"DA"}.</p>
SupportedPrivateKeyFormats StringCollection <p>The supported private key format.</p><p>This value will be used to populate the ServerConfiguration node.</p><p>Default value is empty StringCollection.</p>
MaxTrustListSize Int32 <p>The max size of the trust list.</p><p>This value will be used to populate the ServerConfiguration node.</p><p>Default value is 0.</p>
MultiCastDnsEnabled Boolean <p>The flag for multicast DNS.</p><p>This value will be used to populate the ServerConfiguration node.</p><p>Default value is false.</p>
ReverseConnect Reverse Connect Server Configuration Default value is null. If not specified no reverse connectivity is supported by the server application.
OperationLimits OperationLimits <p>Configures the operation limits of the OPC UA Server.</p><p>Default value is null. </p><p>Note: If OperationLimits is not specified the Server\ServerCapabilities\OperationLimits children nodes will be set on null.</p>

Reverse Connect Server Configuration

The following options are available for configuring the server reverse connections:

Name Type Description
Clients ReverseConnectClientCollection <p>Defines the set of Reverse Connect Client. </p><p>Default value is null.</p>
ConnectInterval Int32 <p>The interval, in milliseconds, after which a new reverse connection is attempted.</p><p>Default value is 15000.</p>
ConnectTimeout Int32 <p>The default timeout, in milliseconds, to wait for a response to a reverse connection.</p><p>Default value is 30000.</p>
RejectTimeout Int32 <p>The timeout to wait, in milliseconds, to establish a new reverse connection after a rejected attempt.</p><p>Default value is 60000.</p>

OperationLimits

Name Type Description
MaxNodesPerRead UInt32 <p>Indicates the maximum size of the nodesToRead array when a Client calls the Read Service.<p><p>Default value is 0. </p>
MaxNodesPerHistoryReadData UInt32 <p>Indicates the maximum size of the nodesToRead array when a Client calls the HistoryRead Service using the historyReadDetails RAW, PROCESSED, MODIFIED or ATTIME.</p><p>Default value is 0. </p>
MaxNodesPerHistoryReadEvents UInt32 <p>Indicates the maximum size of the nodesToRead array when a Client calls the HistoryRead Service using the historyReadDetails EVENTS. </p><p>Default value is 0. </p>
MaxNodesPerWrite UInt32 <p>Indicates the maximum size of the nodesToWrite array when a Client calls the Write Service.</p><p>Default value is 0. </p>
MaxNodesPerHistoryUpdateData UInt32 <p>Indicates the maximum size of the historyUpdateDetails array supported by the Server when a Client calls the HistoryUpdate Service.</p><p>Default value is 0. </p>
MaxNodesPerHistoryUpdateEvents UInt32 <p>Indicates the maximum size of the historyUpdateDetails array when a Client calls the HistoryUpdate Service.</p> <p>Default value is 0. </p>
MaxNodesPerMethodCall UInt32 <p>Indicates the maximum size of the methodsToCall array when a Client calls the Call Service.</p> <p>Default value is 0. </p>
MaxNodesPerBrowse UInt32 <p>Indicates the maximum size of the nodesToBrowse array when calling the Browse Service or the continuationPoints array when a Client calls the BrowseNext Service.</p> <p>Default value is 0. </p>
MaxNodesPerRegisterNodes UInt32 <p>Indicates the maximum size of the nodesToRegister array when a Client calls the RegisterNodes Service and the maximum size of the nodesToUnregister when calling the UnregisterNodes Service.</p> <p>Default value is 0. </p>
MaxNodesPerTranslateBrowsePathsToNodeIds UInt32 <p>Indicates the maximum size of the browsePaths array when a Client calls the TranslateBrowsePathsToNodeIds Service.</p> <p>Default value is 0. </p>
MaxNodesPerNodeManagement UInt32 <p>Indicates </p> <p>- the maximum size of the nodesToAdd array when a Client calls the AddNodes Service,</p> <p>- the maximum size of the referencesToAdd array when a Client calls the AddReferences Service,</p> <p>- the maximum size of the nodesToDelete array when a Client calls the DeleteNodes Service,</p> <p>- the maximum size of the referencesToDelete array when a Client calls the DeleteReferences Service.</p><p>Default value is 0. </p>
MaxMonitoredItemsPerCall UInt32 <p>Indicates </p><p>- the maximum size of the itemsToCreate array when a Client calls the CreateMonitoredItems Service,</p><p>- the maximum size of the itemsToModify array when a Client calls the ModifyMonitoredItems Service,</p> <p>- the maximum size of the monitoredItemIds array when a Client calls the SetMonitoringMode Service or the DeleteMonitoredItems Service,</p> <p>- the maximum size of the sum of the linksToAdd and linksToRemove arrays when a Client calls the SetTriggering Service.</p> <p>Default value is 0. </p>

Reverse Connect Client

The following options are available for configuring the server reverse connect clients:

|Name |Type |Description | | - | - | - | |EndpointUrl |String |<p>The endpoint Url of the reverse connect client endpoint. </p><p>Default value is null.</p>| |Timeout |Int32 |<p>The timeout to wait for a response to a reverse connection.</p><p>Overrides the default reverse connection setting.</p><p>Default value is 0.</p>| |MaxSessionCount |Int32 |<p>The maximum count of active reverse connect sessions.</p><p>· 0 or undefined means unlimited number of sessions.</p><p>· 1 means a single connection is created at a time.</p><p>· n disables reverse hello once the total number of sessions in the server reaches n.</p><p>Default value is 0.</p>| |Enabled|Boolean|<p>Specifies whether the sending of reverse connect attempts is enabled.</p><p>Default value is true.</p>| TraceConfiguration

Tracing configuration (see XML element TraceConfiguration) has the following parameters:

Property Description
OutputFilePath <p>The path of the folder where the file is stored. (Ex. %LocalFolder%\Logs\Opc.Ua.SampleServer.log)</p><p>Default value is null.</p>
DeleteOnLoad <p>If true at configuration load the previous file having same name is deleted.</p><p>Default value is false.</p>
TraceMask <p>The trace mask is composed of the following bit masks.</p><p>0x0 – No messages</p><p>0x1 – Error messages</p><p>0x2 – Informational messages</p><p>0x4 – Stack traces</p><p>0x8 – Basic messages for service calls</p><p>0X10 – Detailed messages for service calls</p><p>0X20 – Basic messages for each operation</p><p>0x40 – Detailed messages for each operation</p><p>0x80 – messages related to Initialization and Shutdown</p><p>0x100- messages related to calls of external systems</p><p>0x200 – messages related to security</p><p>0x7FFFFFFF – All messages</p><p> </p><p>Examples:</p><p>1 - Errors only</p><p>513 – Security and Errors</p><p>515 – Security, Errors and Info</p><p>Default value is 0x0.</p>

See below a sample of tracing configuration:

…

<TraceConfiguration>

  <OutputFilePath>%LocalFolder%\Logs\SampleServer.log</OutputFilePath>
  <DeleteOnLoad>true</DeleteOnLoad>

  
  
  
  
  
  
  
  
  
  
  

  <TraceMasks>519</TraceMasks>

</TraceConfiguration>

At runtime the object ApplicationInstance.ApplicationConfiguration.TraceConfiguration of type TraceConfiguration is holding the actual tracing parameters.

These could be changed programmatically at run time as in the sample below:

//location of the output file application.ApplicationConfiguration.TraceConfiguration.OutputFilePath = "%LocalFolder %\Logs\SampleServer.log";

//whether the configured output file should be deleted at application configuration load application.ApplicationConfiguration.TraceConfiguration.DeleteOnLoad = true;

//mask indicating what logs are traced application.ApplicationConfiguration.TraceConfiguration.TraceMasks = 519;

//apply the settings above application.ApplicationConfiguration.TraceConfiguration.ApplySettings();

Register to Discovery Servers

The registration of the server application to the Local Discovery Server (LDS) service can be configured in the application configuration by setting the value the MaxRegistrationInterval property.

The parameter represents the time in milliseconds between registration attempts.

By setting a value 0 for the parameter the server will not automatically register to Local Discovery Server service.

The default value of the parameter is 30000.

// configure the server to automatically register to Discovery service every 20 seconds. configuration.ServerConfiguration.MaxRegistrationInterval = 20000;

Note: The Local Discovery Server service needs to be installed on the local machine in order to allow the server to user the registration mechanism. Also, the server's certificate must be added to Local Discovery Service trust list in order to be visible to it. For more details see Discovery Server Trust.

Configure Security

In OPC UA there are three different levels of security for the client and server applications:

  • Applications trust using certificates

  • User level security - authentication, authorization

  • Communication encryption

Trust using certificates

OPC-UA applications identify themselves via application instance certificates. With the aid of certificates it is possible to grant certain client applications access to the information on an OPC UA server. For detailed information about OPC UA Security see Introduction to OPC UA > Security. For detailed information about Certificate Validation see Certificate validation and the stack.

A server must be configured for the following stores:

  • own store - where the personal server certificate is stored

  • trusted store - is storing trusted client certificates

  • issuer store - is storing a list of trusted certificate authorities

  • rejected store - is storing invalid client certificate for later review by the administrator

The configuration of the server application can be set inside the code without using a configuration file (xml configuration file).

The code sample below shows how to configure the location of certificate stores for application certificate trusted and rejected certificates.

try
{
  ApplicationConfiguration configuration = new ApplicationConfiguration();
  configuration.SecurityConfiguration = new SecurityConfiguration();
  configuration.SecurityConfiguration.ApplicationCertificate.StorePath = @"%LocalFolder%\\pki\\own";
  configuration.SecurityConfiguration.ApplicationCertificate.SubjectName = "Sample Server";
  configuration.SecurityConfiguration.TrustedPeerCertificates.StorePath = @"%LocalFolder%\\pki\\trusted";
  configuration.SecurityConfiguration.TrustedIssuerCertificates.StorePath = @"%LocalFolder%\\pki\\issuer";
  configuration.SecurityConfiguration.RejectedCertificateStore.StorePath = @"%LocalFolder%\\pki\\rejected";

  // check if the configuration is valid for a server application.
  await configuration.Validate(ApplicationType.Server);
}
catch (Exception e)
{
  Console.WriteLine("Erorr loading application configuration :" + e.Message);
  Environment.Exit(-1);
}

If the server has the client’s certificate in its trust list, but the client does not have the server’s certificate in its trust list, the client application can decide if it wants to accept the invalided server certificate, during runtime. This can be achieved by handling the CertificateValidation event from the ApplicationConfiguration.CertificateValidator entity.

User level security

Authentication

User Authentication is achieved when the Client passes user credentials to the Server via Session services. The Server can authenticate the user with these credentials. There are four authentication types:

  • Anonymous

  • UserName

  • Certificate

  • Ws-Security

The OPC UA .NET Standard SDK Server Library provides support for user authentication.

Authentication configuration

The server application can specify either programmatically or in its application configuration file (see SampleServer.Config.xml) the supported user authentication policies:

<UserTokenPolicies>

 
 <ua:UserTokenPolicy>
      <ua:TokenType>Anonymous\_0</ua:TokenType>
  <ua:SecurityPolicyUri>http://opcfoundation.org/UA/SecurityPolicy#None</ua:SecurityPolicyUri>
 </ua:UserTokenPolicy>
 
 <ua:UserTokenPolicy>
  <ua:TokenType>UserName\_1</ua:TokenType>
  
  <ua:SecurityPolicyUri>http://opcfoundation.org/UA/SecurityPolicy#Basic256Sha256</ua:SecurityPolicyUri>
 </ua:UserTokenPolicy>

 
 <ua:UserTokenPolicy>
  <ua:TokenType>Certificate\_2</ua:TokenType>
  
  <ua:SecurityPolicyUri>http://opcfoundation.org/UA/SecurityPolicy#Basic256Sha256</ua:SecurityPolicyUri>
 </ua:UserTokenPolicy>

 
 <ua:UserTokenPolicy>
  <ua:TokenType>IssuedToken\_3</ua:TokenType>
  
  <ua:SecurityPolicyUri>http://opcfoundation.org/UA/SecurityPolicy#Basic256Sha256</ua:SecurityPolicyUri>
 </ua:UserTokenPolicy>
 
</UserTokenPolicies>

Below is a code snippet for defining the authentication policies programmatically.

application.ApplicationConfiguration.ServerConfiguration.UserTokenPolicies = new UserTokenPolicyCollection();
application.ApplicationConfiguration.ServerConfiguration.UserTokenPolicies.Add(new UserTokenPolicy() { TokenType = UserTokenType.Anonymous });
application.ApplicationConfiguration.ServerConfiguration.UserTokenPolicies.Add(new UserTokenPolicy() { TokenType = UserTokenType.UserName });
application.ApplicationConfiguration.ServerConfiguration.UserTokenPolicies.Add(new UserTokenPolicy() { TokenType = UserTokenType.Certificate });
application.ApplicationConfiguration.ServerConfiguration.UserTokenPolicies.Add(new UserTokenPolicy() { TokenType = UserTokenType.IssuedToken });

The server will expose this in the list of supported user identity tokens during the discovery process.

By removing one of the above token types, the server will not expose it in the list of supported identity tokens and will automatically reject connect requests for that specific token type.

Authorization

OPC UA provides a mechanism to exchange user credentials, but does not specify how the applications use those credentials. Client and Server applications may determine in their own way which data is accessible and which operations are authorized.

In the OPC UA .NET server application, one can authorize the writing of a variable by handling the OnSimpleWriteValue and OnReadUserAccessLevel events. For the code sample see UserAuthentication Node Manager.

For a more general authorization approach, one can override the ValidateRequest from the StandardServer base class. In this case all the service call requests can be authorized with an application specific logic. For the code sample see User Authentication and Authorization.

Communication encryption

One essential mechanism to meet the security objectives is to establish a Secure Channel that is used to secure the communication between a Client and a Server. The Secure Channel provides encryption to maintain Confidentiality, Message Signatures to maintain Integrity, and Digital Certificates to provide application Authentication for data that comes from the Application Layer and passes the “secured” data to the Transport Layer.

The security mechanisms provided by the Secure Channel services are implemented by the stack.

The ApplicationInstanceCertificate is used to secure the OpenSecureChannel request. The MessageSecurityMode and the SecurityPolicy tell the Client how to secure messages sent via the SecureChannel.

Configure Reverse Connect

Starting with version 2.60 the OPC UA .NET Standard SDK provides Reverse Connect functionality.

Upon start-up the server configuration is analysed and if the Reverse Connect section of ServerConfiguration contains a list of ReverseConnectClient objects, for each ReverseConnectClient instance from configuration an instance of ReverseConnectDetails class will be added to server's reverse connections list.

Sample configuration section:

// Reverse connect settings  
<ServerConfiguration>

 ...

 <ReverseConnect>
  <Clients>
    <ReverseConnectClient>
     <EndpointUrl>opc.tcp://localhost:61512</EndpointUrl>
     <Timeout>30000</Timeout>
     <MaxSessionCount>0</MaxSessionCount>
     <Enabled>true</Enabled>
    </ReverseConnectClient>
  </Clients>

  <ConnectInterval>10000</ConnectInterval>
  <ConnectTimeout>30000</ConnectTimeout>
  <RejectTimeout>20000</RejectTimeout>

 </ReverseConnect>

</ServerConfiguration>

The server that has the above configuration will send ReverseHello messages to the opc.tcp://localhost:61512 endpoint . The client responds with a Hello message then the server creates a new reverse connection that is managed by the server. A single connection is established to the client. If an established reverse connection is temporary lost the server will automatically re-establish the connection when the client is up again.

The server configuration options can be extended with the Reverse Connect settings in the configuration file SampleServer.xml.

Security Configuration

In order to establish a trusted relationship between a client and a server, a reciprocal copy of their certificates is needed, i.e. the public key certificate of the client application needs to be copied to the trust list of the server application and the public key certificate of the server application needs to be copied to the trust list of the client application.

Use case: we have a server application called UAServer.exe and a client application called UAClient.exe using self-signed certificates and we want to configure a trust relationship between these two.

The client application is located at “D:\UA Client\UAClient.exe” and it has the following security configuration parameters:

application.Configuration.Security.ApplicationCertificateStore = @"D:\UA Client\ApplicationCertificate";
application.Configuration.Security.ApplicationCertificateSubject = "UAClient";
application.Configuration.Security.TrustedCertificateStore = @"D:\UA Client\TrustedCertificates";
application.Configuration.Security.TrustedIssuerCertificateStore = @"D:\UA Client\TrustedCAs";
application.Configuration.Security.RejectedCertificateStore = @"D:\UA Client\RejectedCertificates";
application.Configuration.Security.UserIssuerCertificates = @"D:\UA Client\TrustedUserCAs";
application.Configuration.Security.TrustedUserCertificates = @"D:\UA Client\TrustedUserCertificates";

The server application is located at “D:\UA Server\UAServer.exe” and it has the following security configuration in the configuration file:

<SecurityConfiguration>

	
	<ApplicationCertificate>
		<StoreType>Directory</StoreType>
		<StorePath>D:\UA Server\ApplicationCertificate</StorePath>
		<SubjectName>UAServer</SubjectName>
	</ApplicationCertificate>

	
	<TrustedIssuerCertificates>
		<StoreType>Directory</StoreType>
		<StorePath>D:\UA Server\TrustedCAs</StorePath>
	</TrustedIssuerCertificates>

	
	<TrustedPeerCertificates>
		<StoreType>Directory</StoreType>
		<StorePath>D:\UA Server\TrustedCertificates</StorePath>
	</TrustedPeerCertificates>

	
	<NonceLength>32</NonceLength>

	
	<RejectedCertificateStore>
		<StoreType>Directory</StoreType>
		<StorePath>D:\UA Server\RejectedCertificates</StorePath>
	</RejectedCertificateStore>

	
	<AutoAcceptUntrustedCertificates>false</AutoAcceptUntrustedCertificates>  

	
	<UserIssuerCertificates>
		<StoreType>Directory</StoreType>
		<StorePath>D:\UA Server\TrustedUserCAs</StorePath>
	</UserIssuerCertificates>

	
	<TrustedUserCertificates>
		<StoreType>Directory</StoreType>
		<StorePath>D:\UA Server\TrustedUserCertificates</StorePath>
	</TrustedUserCertificates>

 </SecurityConfiguration>

The trust relationship between the client and server applications can be configured by performing the following steps:

  1. Locate the public key certificate of the client application. This is represented by the “D:\UA Client\ApplicationCertificate\certs\UAClient [E8B470047EB9FD590E01B4AEC74D16B3EE5C03A4].der” file.
  2. Copy this file to the “D:\UA Server\TrustedCertificates\certs” folder (the trust list of the server).
  3. Locate the public key certificate of the server application. This is represented by the “D:\UA Server\ApplicationCertificate\certs\UAServer [7A71315A0409EA583C88B30D056A54E32DE62548].der” file.
  4. Copy this file to the “D:\UA Client\TrustedCertificates\certs” folder (the trust list of the client).

After above steps the client and server applications should become mutually trusted.

The client and server applications can be located on the same machine or on different machines accessible on a computer network.

The steps above are sufficient for OPC UA applications which use self-signed application instance certificates.

If a certificate is deleted from the trust list of an application it will not be trusted for further authentication attempts.

For applications using certificates signed by a Certificate Authority

UA Applications can use application instance certificates issued by a Certificate Authority (CA). In order to establish a trusted relationship between two applications with certificates issued by a Certificate Authority, the public key certificate of the CA (in a .der file format) needs to be copied in the list of trusted CAs for each application.

Use case: Let’s assume we have the same applications as above (UAServer.exe and UAClient.exe) but this time they are using certificates issued by a CA.

The CA’s private and public keys are represented by their .pfx and .der files, located for example in the “D:\CA File” folder.

  • Private key : “D:\CA File\pki\own\private\CA1.pfx”

  • Public key : “D:\CA File\pki\own\certs\CA1.der” file.

The following steps should be performed to establish a trust relationship between the client and server application:

  1. Locate the public key file of the CA (In our example “D:\CA File\pki\own\certs\CA1.der”).
  2. Copy this file to the “D:\UA Server\pki\TrustedCAs\certs folder (the trusted CAs list of the server).
  3. Copy again this file to the “D:\UA Client\pki\TrustedCAs\certs folder (the trusted CAs list of the client).

Configure the Endpoints

An endpoint is a physical address available on a network that allows Clients to access one or more Services provided by a Server. Each Server may have multiple endpoints. The address of an endpoint must include a HostName.

Every Server shall have a Discovery Endpoint that Clients can access without establishing a Session. This Endpoint may or may not be the same Session Endpoint that Clients use to establish a SecureChannel. Clients read the security information necessary to establish a SecureChannel by calling the GetEndpoints Service on the Discovery Endpoint.

A Server may support multiple security configurations for the same Endpoint. In this situation, the Server will have separate EndpointDescription records for each available configuration. The security configuration for an Endpoint has four components:

  • Server Application Instance Certificate

  • Message Security Mode

  • Security Policy

  • Supported User Identity Tokens

In order to expose endpoints in the server, the following configurations must be set:

The address (endpointUrl) of the endpoint exposed by the server can be set from application configuration in BaseAddresses property.

configuration.ServerConfiguration = new ServerConfiguration();

configuration.ServerConfiguration.BaseAddresses.Add("opc.tcp://localhost:62552/MyUaServer");

The application certificate of the endpoint is the same as the application instance certificate of the server (configured in Other server configuration).

The security mode and the security policy of the endpoint are set, by setting the security policies of the server configuration. The list of exposed endpoints is constructed from all possible combination of addresses and exposed security policies.

configuration.ServerConfiguration.SecurityPolicies = new ServerSecurityPolicyCollection();

configuration.ServerConfiguration.SecurityPolicies.Add(new ServerSecurityPolicy()
{

  SecurityLevel = 1,
  SecurityMode = MessageSecurityMode.None,
  SecurityPolicyUri = "http://opcfoundation.org/UA/SecurityPolicy#None"

});

configuration.ServerConfiguration.SecurityPolicies.Add(new ServerSecurityPolicy()
{

  SecurityLevel = 3,
  SecurityMode = MessageSecurityMode.SignAndEncrypt,
  SecurityPolicyUri = "http://opcfoundation.org/UA/SecurityPolicy#Basic256Sha256"

});

The user token policies of the endpoint are configured in the server configuration, as the following example. Each endpoint of the server will expose the configured user token policies.

configuration.ServerConfiguration.UserTokenPolicies = new UserTokenPolicyCollection();
configuration.ServerConfiguration.UserTokenPolicies.Add(new UserTokenPolicy() { TokenType = UserTokenType.Anonymous });
configuration.ServerConfiguration.UserTokenPolicies.Add(new UserTokenPolicy() { TokenType = UserTokenType.UserName });

In order to test the exposed endpoints, you need to start the server, and with a UA Client call the GetEnpoints service on the server. This service will return the endpoint descriptions for all available endpoints. You can check all the properties of the endpoint description.

After configuring the endpoints, the next steps are to complete Other server configuration and User Application configuration configurations.

Logging

Server tracing mechanism is described in Logging chapter.

ServerToolkitConfiguration

ServerToolkitConfiguration class holds specific OPC UA .NET Standard SDK Server Library configuration.

Parameter Description
TrustedIssuerUserCertificateStore <p>Deprecated starting with version 2.10.</p><p>Use** ApplicationConfiguration.SecurityConfiguration.UserIssuerCertificates instead. </p>
TrustedUserCertificateStore <p>Deprecated starting with version 2.10.</p><p>Use** ApplicationConfiguration.SecurityConfiguration.TrustedUserCertificates instead. </p>
RejectedUserCertificateStore <p>Deprecated starting with version 2.10.</p><p>Use** ApplicationConfiguration.SecurityConfiguration.RejectedCertificateStore instead. </p>
ServerCertificateLifeTime <p>Gets or sets the lifetime of the Server Certificate in months.</p><p>Default value is 12.</p>

Note: All deprecated properties are ignored by current implementation of OPC UA .NET Standard SDK Server Library.

Nodes

Define Namespaces

The namespace is a URI that identifies the naming authority responsible for assigning an identifier to a node in the server Address Space. Naming authorities include the local Server, the underlying system, standards bodies and consortia. It is expected that most Nodes will use the URI of the Server or of the underlying system. Using a namespace URI allows multiple OPC UA Servers attached to the same underlying system to use the same identifier to identify the same Object. This enables Clients that connect to those Servers to recognize Objects that they have in common.

NamespaceIndex

Namespace URIs, like Server names, are identified by numeric values in OPC UA Services to permit more efficient transfer and processing (e.g. table lookups). The numeric values are used to identify namespaces correspond to the index into the NamespaceArray. The NamespaceArray is a Variable that is part of the Server Object in the AddressSpace.

So each server has at least a namespace identified by a URI and a NamespaceIndex. When you define your server you must create a new Namespace URI that you will use to define your own nodes. The namespace URI is case sensitive. There are already some namespaces used in the OPC UA .NET Standard SDK application:

NamespaceIndex NamespaceURI Namespace Description
0 http://opcfoundation.org/UA/ The URI for the OPC UA namespace
last position in the Namespace Table http://opcfoundation.org/UA/Diagnostics The URI for Diagnosis nodes

Create a new namespace

Before defining any nodes for your server you must define a Namespace URI that you will use for identifying your nodes. The URI can have this format:

"http://yourcompany.com/NodeGroup"

and must be provided to your customized Server Node Manager constructor like this:

public class CustomizedNodeManager : NodeManager
{

  public const string NamespaceURI = "http://yourcompany.com//NodeGroup1";

  public CustomizedNodeManager(IServerInternal server, ApplicationConfiguration configuration) : base(server, configuration, NamespaceURI)

…

Multiple namespaces can be defined for the same Node Manager like this:

public class CustomizedNodeManager : NodeManager
{
     public const string NamespaceURI1 = "http://yourcompany.com//NodeGroup1";
     public const string NamespaceURI2 = "http://yourcompany.com//NodeGroup2";
     public CustomizedNodeManager(IServerInternal server, ApplicationConfiguration configuration) : base(server, configuration, NamespaceURI1, NamespaceURI2)
…

To have an overview of all added Namespace URIs you can call for your customized Node Manager this.NamespaceUris property.

The NamespaceIndex for each URI will be allocated automatically by the OPC UA .NET Standard SDK Server Library. To access the Server NamespaceTable and visualize allocated NamespaceIndexes you can call this.Server.NamespaceUris property.

For more information about the Node Manager see Node Managers.

Usage of namespaces

The Namespaces are used:

  • to define Node identifiers called NodeIds

  • to define BrowseNames, a human-readable name that contains a namespace and a string. The namespace is provided to make the BrowseName unique in some cases in the context of a Node although not unique in the context of the Server. If different organizations define BrowseNames for Properties, the namespace of the BrowseName provided by the organization makes the BrowseName unique.

NodeId

Each node in the Server's Address Space is uniquely identified by a NodeId. A server shall persist the NodeId of a Node, that is, it shall not generate new NodeIds when rebooting. NodeId is made up of a NamespaceIndex and a code which can be:

  • Numeric value

  • String value

  • GUID - Globally Unique Identifier

  • Opaque - Namespace specific format

OPC UA .NET Standard Stack from OPC Foundation provides a NodeId class for defining and working with NodeIds. The class has a ToString() method and Text property for representing NodeId as a string. Some examples of NodeId strings are:

  • "ns=10;i=-1" - "ns" means NamespaceIndex , "i" means a numeric NodeId is used

  • "ns=10;s=Hello:World" - "s" means a string NodeId is used

  • "ns=10;g=09087e75-8e5e-499b-954f-f2a9603db28a" - "g" means a GUID type NodeId is used

  • "ns=1;b=M/RbKBsRVkePCePcx24oRA==" - "b" means an opaque NodeId is used

A null NodeId has special meaning. For example, many services define special behaviour if a null NodeId is passed as a parameter but pay attention because a Node in the AddressSpace shall not have a null as its NodeId.

Node Managers

In the OPC UA .NET Standard SDK Server Library the node managers are entities that handle the logic of creating and maintaining a set of nodes from the address space, with a certain namespace index. The node manager must inherit the NodeManager base class of the OPC UA .NET Standard SDK Server Library. Further they will be described the main methods that can be overridden in the derived node manager class.

Constructor

The recommended constructor of the derived node manager class takes at least two parameters: the internal server, and the application configuration. The new node manager should be instantiated from the CreateMasterNodeManager() method override of the UaServer class. SampleServer Class provides another example of the CreateMasterNodeManager() method override.

protected override MasterNodeManager CreateMasterNodeManager(IServerInternal server, ApplicationConfiguration configuration)
{
	Utils.Trace(Utils.TraceMasks.Information, "MySampleServer.CreateMasterNodeManager", "Creating the Node Managers.");

	List<INodeManager> nodeManagers = new List<INodeManager>();  
	//Create the custom node managers

	nodeManagers.Add(new MyNodeManager(server, configuration));  

	//create the master node manager
	return new MasterNodeManager(server, configuration, null, nodeManagers.ToArray());
}

.....

//initializes the node manager
public MyNodeManager(
  IServerInternal server,
  ApplicationConfiguration configuration) : base(server, configuration, "http://yourcompany.com//NodeGroup1")
{
  …
}

For more information about defining the namespace URIs for the node manager please read Define namespaces topic.

Create Address Space

Override this method in order to create and add nodes to the server's address space. Read more on how to create an address space programmatically here Create Static Address Space.

New() method override

Provides the possibility to specify a custom logic for creating new node ids for a node manager.

When overriding this method please ensure that the new NodeId is unique over the entire server address space.

Default implementation of New() method from OPC UA .NET Standard SDK Server Library 's NodeManager creates NodeIds with numeric identifier (e.g. "ns=10;i=1").

The following sample creates a GUID type NodeId:

public override NodeId New(ISystemContext context, NodeState node)
{
  return new NodeId(new Guid(), NamespaceIndex);
}

Other

Besides the methods presented above, the new node manager can override methods for the services regarding:

  • Monitored Items

  • Read and Write

  • Method Call

  • Delete nodes and references

  • History read and write

  • Conditions

Create Static Address Space

Server’s address space is a set of nodes accessible to clients. Nodes in the address space are used to represent real objects, their definitions and their references between them. Server is free to organize its nodes within the address space. The use of References between nodes permits the server to organize its address space into a hierarchy, a full mesh network of nodes, or any possible mix. For more information about OPC UA address space read Address Space.

Prerequisites

For creating an address space programmatically we need to override the CreateAddressSpace method in your NodeManager class (see Node Managers). The code explained below must be included inside the CreateAddressSpace method.

Define nodes

This article explains how to define the most frequently used nodes in a server using a set of predefined methods from NodeManager.

The methods listed below New() method to generate the NodeId for the newly created node. The existing implementation of New() method from UaServer class generate unique NodeIds within server's address space.

Create Object Nodes

  • Create a BaseObjectState instance with the specified name and add it as a child of specified parent in the address space hierarchy. BaseObjectState refrigerator = CreateObject(parent, "Refrigerator");

  • Create an object node of a type inferred from typeDefinition parameter, with specified name and add it as a child of specified parent in the address space hierarchy: FolderState root = CreateObjectFromType(null, "DataAccess", ObjectTypeIds.FolderType, ReferenceTypeIds.Organizes) as FolderState;

CreateObjectFromType() is the most powerful method for creating object nodes, it creates all types of nodes available in ObjectTypeIds class from OPC UA .NET Standard Stack from OPC Foundation.

Note: Starting with version 2.60 the CreateObjectFromType() method has the createOptionalProperties parameter that specifies if the properties that have the optional modelling rule are instantiated in the resulting object. The default value is true. Note that the implementation from OPC UA .NET Standard Stack from OPC Foundation instantiates only mandatory fields.

  • CreateFolder() method is a very specialized method for creating FolderState nodes which are a subset of object nodes. The following code sample creates an identical object with the one created by the sample code from CreateObjectFromType() method : FolderState root = CreateFolder(null, "DataAccess");

Create Variable Nodes

  • Create a BaseDataVariableState instance, assign its DataType property with dataType parameter value, set its ValueRank property to valueRank parameter (or Scalar if parameter is omitted) and add it as a child of parameter parent node (if not null). Both calls produce identical output:

BaseDataVariableState scalarInstructions = CreateVariable(scalarFolder, "Scalar_Instructions", DataTypeIds.String, ValueRanks.Scalar);

// or

BaseDataVariableState<string> scalarInstructions = CreateVariable(scalarFolder, "Scalar_Instructions");

If the dataType parameter of CreateVariable() method is pointing to a data type loaded from a dictionary from a nodeSet xml file, the returned object will have the Value property set to a new instance of Softing.Opc.Ua.Server.Types.StructuredValue type that is built according to the data type definition.

  • Create a variable node of a type inferred from typeDefinition parameter, with specified name and add the specified referenceType to the parent parameter: Sample call will create a new instance of DataItemState:

    // Create CoolingMotorRunning variable DataItemState coolingMotorRunning = CreateVariableFromType(refrigerator, "CoolingMotorRunning", VariableTypeIds.DataItemType, ReferenceTypeIds.Organizes, true) as DataItemState; coolingMotorRunning.DataType = DataTypeIds.Boolean; coolingMotorRunning.ValueRank = ValueRanks.Scalar; coolingMotorRunning.Value = true;

CreateVariableFromType() is the most powerful method for creating variable nodes, it creates all types of nodes available in VariableTypeIds class from OPC UA .NET Standard Stack from OPC Foundation.

Note: Starting with version 2.60 the CreateVariableFromType() method has the createOptionalProperties parameter that specifies if the properties that have the optional modelling rule are instantiated in the resulting variable object. The default value is true. Note that the implementation from OPC UA .NET Standard Stack from OPC Foundation instantiates only mandatory fields.

It is possible to create a Variable node that has a DataType that was loaded from a type dictionary and to provide a value for the newly created node. Below there is a sample of how to create a new instance of Softing.Opc.Ua.Server.Types.StructuredValue and assign it as the value of a BaseVariableState node. Please be aware that it is very important to set the ExpandedNodeId value to the StructuredValue.TypeId property. The field values can be modified but it is mandatory to set values of the exact field type for that particular StructuredField instance:

var dataTypeRefrigeratorStatus = PredefinedNodes.Values.FirstOrDefault(x => x.BrowseName.Name == "RefrigeratorStatusDataType");
var refrigeratorStatusVariable = this.CreateVariableFromType(referenceServerVariables, "RefrigeratorStatusVariable", VariableTypeIds.BaseVariableType, ReferenceTypeIds.Organizes);
refrigeratorStatusVariable.DataType = dataTypeRefrigeratorStatus.NodeId;

StructuredValue refrigeratorStatusValue = new StructuredValue();

refrigeratorStatusValue.TypeId = NodeId.ToExpandedNodeId(dataTypeRefrigeratorStatus.NodeId, Server.NamespaceUris);
refrigeratorStatusValue.Initialize(Server.Factory);
refrigeratorStatusVariable.Value = refrigeratorStatusValue;

The methods described below are creating the most common types of variable nodes. Using them results in smaller code footprint in your NodeManager class implementation and faster address space definition.

  • CreateProperty() method creates a PropertyState object, adds it to the specified parent node and sets all required properties for this variable type. The following code samples produce similar output:

    // A property to report the process state PropertyState<string> state = CreateProperty<string>(process, "LogFilePath"); state.AccessLevel = AccessLevels.CurrentReadOrWrite; state.UserAccessLevel = AccessLevels.CurrentReadOrWrite; state.Value = ".\Log.txt";

And:

// A property to report the process state
PropertyState state = CreateProperty(process, "LogFilePath", DataTypeIds.String);              
state.AccessLevel = AccessLevels.CurrentReadOrWrite;
state.UserAccessLevel = AccessLevels.CurrentReadOrWrite;
state.Value = ".\\Log.txt";
  • CreateDataItemVariable() method creates a DataItemState object, adds it to the specified parent node and sets all required properties for this variable type:

    // Create DoorMotor variable DataItemState doorMotor = CreateDataItemVariable(refrigerator, "DoorMotor", DataTypes.Double); doorMotor.Value = 11.2;

  • CreateAnalogVariable() method creates an AnalogItemState object, adds it to the specified parent node and sets all required properties for this variable type:

    // Create MotorTemperature variable m_motorTemperature = CreateAnalogVariable(refrigerator, "MotorTemperature", DataTypeIds.Double, ValueRanks.Scalar, new Range(90, 10), null); m_motorTemperature.InstrumentRange.Value = new Range(100, 0); m_motorTemperature.Value = 47.6;

  • CreateTwoStateDiscreteVariable() method creates an TwoStateDiscreteState object, adds it to the specified parent node and sets all required properties for this variable type:

    // Create a new TwoStateDiscreteState variable TwoStateDiscreteState variable = CreateTwoStateDiscreteVariable(parent, "001", "red", "blue"); variable.TrueState.AccessLevel = AccessLevels.CurrentReadOrWrite; variable.TrueState.UserAccessLevel = AccessLevels.CurrentReadOrWrite; variable.FalseState.AccessLevel = AccessLevels.CurrentReadOrWrite; variable.FalseState.UserAccessLevel = AccessLevels.CurrentReadOrWrite;

  • CreateMultiStateDiscreteVariable() method creates an MultiStateDiscreteState object, adds it to the specified parent node and sets all required properties for this variable type:

    // Create a new MultiStateDiscreteState variable

    MultiStateDiscreteState variable = CreateMultiStateDiscreteVariable(parent, "001", "open", "closed", "jammed");

  • CreateInstanceFromType() method creates an BaseInstanceState object that has the specified Type definition.

    // Create a new MultiStateDiscreteState variable BaseInstanceState instance = CreateInstanceFromType(parent, "001", VariableTypeIds.AnalogItemType); if (instance != null) { AddChildToParent(parent, instance); AddPredefinedNode(SystemContext, instance); }

Create Method Nodes

CreateMethod() from base NodeManager class is a simplified way to create method objects by only providing a parent node, a name, input and output arguments and the handler for method execution.

  // Create OpenCloseDoor method
  Argument[] inputArgs = new Argument[]
  {

     new Argument { Name = "OpenCloseDoor", Description = "Opens/closes the door.",  DataType = DataTypeIds.Boolean, ValueRank = ValueRanks.Scalar }

  };

MethodState openCloseDoorMethod = CreateMethod(refrigerator, "OpenCloseDoor", inputArguments: inputArgs);

openCloseDoorMethod.OnCallMethod = DoOpenCloseDoorCall;

CreateMethod() method allows also to specify a custom method type to be instantiated instead of default MethodState type.

Note: If for any of the above methods(CreateObject(), CreateVariable() etc.), the parent parameter is null the created node is not added in address space hierarchy and in order to find it, via Browse operations, a reference to another node from address space must be added. AddReference() method is suitable for this purpose. In the following sample the new node is linked to ObjectsFolder:

FolderState root = CreateObjectFromType(null, "DataAccess", ObjectTypes.FolderType, ReferenceTypes.Organizes) as FolderState;

AddReference(root, ReferenceTypeIds.Organizes, true, ObjectIds.ObjectsFolder, true);

For creating other types of nodes see Generate Events and Alarms and Conditions.

Add references

A reference is an explicit relationship from one node to another. The node that contains the reference is the source node, and the referenced node is the target node. All references are defined by ReferenceTypes. The NodeManager implementation provides a method for adding references called AddReference().

FolderState root = CreateObjectFromType(null, "DataAccess", ObjectTypes.FolderType, ReferenceTypes.Organizes) as FolderState;

AddReference(root, ReferenceTypeIds.Organizes, true, ObjectIds.ObjectsFolder, true);

Add node to address space

All the nodes created using one of the methods presented above are added automatically in the address space hierarchy. The nodes created using the OPC UA .NET Standard Stack from OPC Foundation basic method calls still need AddPredefinedNode() call to make the node visible in the address space.

Import NodeSet2 Xml

OPC UA .NET Standard SDK Server Library provides support for loading the address space of the Servers from XML files defining a specific structure of nodes or an OPC UA information model.

For this, OPC UA specifications offer a standard way of defining a subset of a UA address space using an XML representation.

OPC Foundation provides the format of this standard in a UaNodeSet.xsd (XML Schema Definition) file available at the following location:

http://opcfoundation.org/UA/2011/03/UANodeSet.xsd

The NodeSet xml files imported by the OPC UA .NET Standard SDK should be compliant with this UaNodeSet schema definition.

An example of implementation is provided by the NodeSetImport Node Manager.

Import a NodeSet file

The NodeManager.ImportNodeSet() method loads the nodes defined in the specified NodeSet xml file and exposes them in the server's address space.

CustomNodeManager2.ImportNodeSet() method has some overloads:

  • XmlElement[] ImportNodeSet(ISystemContext context, string filePath, DuplicateNodeHandling duplicateNodeHandling, out List<string> newNamespaceUris) Possible values for DuplicateNodeHandling enumeration are:

  • ReportError = 0: when a duplicate node is found an exception is thrown and no node is imported.

  • UseExistingNode = 1: when a duplicate node is found the newly imported node is ignored, the node from the address space remains unchanged.

  • OverwriteNode = 2: when a duplicate node is found the newly imported node will replace the node from the address space. The newNamespaceUris out parameter will contain the newly added namespace URIs.

Note: When using DuplicateNodeHandling.OverwriteNode parameter with a NodeSet XML file, all existing nodes are deleted from their original NodeManager's PredefinedNodes collection and then they are added to current NodeManager's PredefinedNodes collection. If the imported NodeSet XML contains nodes defined in namespace 0 (http://opcfoundation.org/UA/) there is a high possibility to make the server unusable because the Server nodes may be overwritten.

  • XmlElement[] ImportNodeSet(ISystemContext context, string filePath, DuplicateNodeHandling duplicateNodeHandling) When the newNamespaceUris parameter is omitted the resulted namespace URIs will not be available.

  • XmlElement[] ImportNodeSet(ISystemContext context, string filePath) When the DuplicateNodeHandling parameter is omitted the DuplicateNodeHandling.UseExistingNode value will be used.

The following code is a sample of calling NodeManager.ImportNodeSet() method.

/// <summary>
/// Loads a UANodeSet xml file into current address space.
/// </summary>
/// <param name="filePath">The path of the fine containing the model.</param>
/// <param name="duplicateNodeHandling">Specifies how to handle a node id that already exists in address space.</param>
private void ImportNodeSet(string filePath, DuplicateNodeHandling duplicateNodeHandling)
{

  try
  {
     XmlElement[] extensions = ImportNodeSet(SystemContext, filePath, duplicateNodeHandling);
  }
  catch (Exception ex)
  {
     Console.WriteLine("Error loading node set: {0}", ex.Message);
     throw;
  }

}

The UANodeSet structure may contain some vendor defined data in the Extensions field. This data will be returned by the ImportNodeSet() method.

Custom vendor information can also be provided as Extensions for each UANode in the file. This data is accessible at application level by overriding the AddBehaviourToPredefinedNode method of the NodeManager class.

/// <summary>
/// Add behaviour to predefined node
/// </summary>
/// <param name="context"></param>
/// <param name="predefinedNode"></param>
/// <returns></returns>
protected override NodeState AddBehaviourToPredefinedNode(ISystemContext context, NodeState predefinedNode)
{
  // This override will receive a callback every time a new node is added
  // e.g. The extension data can be received in predefinedNode.Extensions

  return predefinedNode;
}

The NodeManager.ImportNodeSet() method will parse all data type dictionaries and DataTypeDefinition attribute of data type nodes from the NodeSet XML file and will make possible the instantiation of data types from type dictionaries. The following code illustrates how to create an instance from a data type from an imported data type dictionary:

var dataTypeRefrigeratorStatus = PredefinedNodes.Values.FirstOrDefault(x => x.BrowseName.Name == "RefrigeratorStatusDataType");

// call NodeManager.GetDefaultValueForDatatype to create a new instance of desired data type
StructuredValue refrigeratorStatusValue = GetDefaultValueForDatatype(dataTypeRefrigeratorStatus) as StructuredValue;

// it is possible to change the data type fields values using the indexer by field name.

// Be aware that this change can raise exceptions if the field name is wrong or the value does not match field type

refrigeratorStatusValue["MotorTemperature"] = 5.5;

Nodes with Values (former Attach Input Output)

The server application may support data that is collected based on a sampling model or generated based on an exception-based model. The fastest supported sampling interval may be equal to 0, which indicates that the data item is exception-based rather than being sampled at some period. An exception- sampling based model means that the underlying system does not require and reports data changes.

The values received from the underlying system of the server, are stored in the cache of the SDK. From here the values are sent to the clients via read requests or via data changes. If a read to the underlying system must be made when a read request is received from a client, the server application can handle the OnSimpleReadValue event of the node and make the read from the device, before responding to the client with the newest value.

For writing the values back to the underlying system, the server application must handle the OnSimpleWriteValue event handler of the variable node.

Report value changes

The new values coming from the underlying system for a variable must be set accordingly, as they are received:

variable.Value = GetNewValue();

At this point the new value of the variable is set in the server's cache. The read requests received for this variable from now on will return the new value.

But the value is not reported to the monitored items of the variable unless it is not explicitly asked by calling the ClearChangeMask method.

variable.ClearChangeMasks(context, false);

The method receives two parameters: the system context and the flag that specifies whether to notify the changes of the child nodes as well.

Write values

For writing the values back to the underlying system, the server application can handle the OnSimpleWriteValue event handler of the variable node. The event is raised when a write request is initiated by a client application.

variable.OnWriteValue = OnWriteValue;

....

public ServiceResult OnWriteValue(ISystemContext context, NodeState node, ref object value)
{  
  //Handle the writing to the underlying system
}

Read values

For reading the values from the underlying system on each read request, the server application can handle the OnSimpleReadValue event handler of the variable node. The event is raised when a read request is initiated by a client application. If the client application specifies a max age value which is larger than the age of the cached value, the OnSimpleReadValue callback is not called by the SDK. The age of the value is computed based on the difference between the server timestamp of the cached value and the time when the server starts processing the read request.

Note: If there is a monitored item created for that particular node the internal implementation of monitored item needs to read node value and thus the OnSimpleReadValue event is triggered. In this situation it is advised to do an additional check for context.SessionId inside OnSimpleReadValue handler to see if the OnSimpleReadValue event is triggered from internal stack implementation or if it is part of a session call.

    variable.OnSimpleReadValue = OnSimpleReadValueHandler;
    
    ....
    
    private ServiceResult OnSimpleReadValueHandler(ISystemContext context, NodeState node, ref object value)
    {
      // check if the read is from a UA Client
      **if (context != null && context.SessionId != null)**
      {
         // do the client read handler here
         value = GetNewValue(node);
      }
      else
      {
         // this is the call from monitored item internal implementation
      }
  
      return ServiceResult.Good;
   }

For reading the status code and the timestamp as well (besides the value of the node), the OnReadValue callback must be handled. It transmits all the read parameters, unlike the OnSimpleReadValue callback.

variable.OnReadValue = OnReadValueHandler;

....

private ServiceResult OnReadValueHandler(ISystemContext context, NodeState node, NumericRange indexRange, QualifiedName dataEncoding, ref object value, ref StatusCode statusCode, ref DateTime timestamp)
{
  ....

  value = GetNewValue(node);
  statusCode = StatusCodes.Good;
  timestamp = DateTime.UtcNow;

  ....
}

If the client application specifies a max age value which is larger than the age of the cached value, the OnReadValue callback is not called by the SDK.

Method Calls

Methods are “lightweight” functions, whose scope is bounded by an owning Object, similar to the methods of a class in object -oriented programming or an owning ObjectType, similar to static methods of a class.

Methods are invoked by a client, proceed to completion on the Server and return the result to the client. The lifetime of the Method’s invocation instance begins when the client calls the Method and ends when the result is returned.

Define a method in the server

Methods can be defined inside the CreateAddressSpace method in your node manager class(see Create Static Address Space).

Handle time consuming method calls

Methods can be time consuming but this is not an issue for server as each method call is implemented in OPC UA .NET Standard Stack from OPC Foundation on a separate thread. Server will be never blocked by a method call but client must be carefully implemented taking into account the OperationTimeout representing the number of milliseconds to wait for a response from the Server. For more information about OperationTimeout see Transport Quotas in Other Server Configuration.

Custom Types

The OPC UA servers implemented using OPC UA .NET Standard SDK Server Library have all the standard types defined by the OPC UA specification.

Sometimes it is needed to extend server's address space with custom type definitions and it can be done either by importing the nodes from a NodeSet2.xml file (see Import NodeSet2 Xml) or by creating the required nodes in type definition node hierarchy.

OPC UA .NET Standard SDK Server Library provides helper methods for creating Custom Data Types, Custom Variable Types and Custom Object Types.

Custom types are implemented in Softing.Opc.Ua.Server.Types namespace.

Custom Data Types

The NodeManager class from OPC UA .NET Standard SDK Server Library provides a method to create a custom data type called CreateDataType():

public DataTypeState CreateDataType(NodeId baseDataTypeId, string name, DataTypeDefinition dataTypeDefinition)

CreateDataType() method creates a new DataTypeState object with the specified name, sets its SuperTypeId to the specified baseDataTypeId and the DataTypeDefinition attribute to the specified dataTypeDefinition.

The DataTypeDefinition type is described in OPC Unified Architecture Specification Part 3 and it is implemented in OPC UA .NET Standard Stack from OPC Foundation.

The CreateDataType() method can create the following custom data types:

  • Custom Enumeration as subtype of Enumeration data type. To create a simple custom Enumeration one shall use CreateDataType(DataTypeIds.Enumeration, <new type name>, <EnumDefinition instance>).

      // create an EnumDefinition
      EnumDefinition engineStateEnum = new EnumDefinition();
    
      engineStateEnum.Fields = new EnumFieldCollection()
      {
        new EnumField() { Name = "Stopped", Value = 0},
        new EnumField() { Name = "Running", Value = 1}
      };
    
      // call CreateDataType with baseDataTypeId = DataTypeIds.Enumeration 
      DataTypeState engineStateType = CreateDataType(DataTypeIds.Enumeration, "EngineStateType", engineStateEnum);
    

Note: All Enumeration types shall have DataTypeIds.Enumeration as super type id. They shall not be sub typed.

  • Custom Enumeration as subtype of Uinteger data type, we call it option set Enumeration. To create a custom option set Enumeration one shall use CreateDataType(DataTypeIds.<subtype of UInteger>, <new type name>, <EnumDefinition instance>).

      // define an option set enumeration. All EnumField values must be 2n. 
      EnumDefinition displayWarningEnum = new EnumDefinition();
      displayWarningEnum.Fields = new EnumFieldCollection()
      {
        new EnumField() { Name = "ABS", Value = 1},
        new EnumField() { Name = "ESP", Value = 2},
        new EnumField() { Name = "TirePressure", Value = 4},
        new EnumField() { Name = "CheckEngine", Value = 8},
        new EnumField() { Name = "OpenDoor", Value = 16},
      };
    
      // call CreateDataType with baseDataTypeId = DataTypeIds.UInt16 
      DataTypeState displayWarningType = CreateDataType(DataTypeIds.UInt16, "DisplayWarningType", displayWarningEnum);
    
  • Custom OptionSet as subtype of OptionSet data type. To create a custom OptionSet one shall use CreateDataType(DataTypeIds.OptionSet, <new type name>, <EnumDefinition instance>).

      // define an option set type. All EnumField values must be 2n. 
      EnumDefinition featuresEnum = new EnumDefinition();
      featuresEnum.Fields = new EnumFieldCollection()
      {
        new EnumField() { Name = "ABS", Value = 1},
        new EnumField() { Name = "ESP", Value = 2},
        new EnumField() { Name = "AirbagPassenger", Value = 4},
        new EnumField() { Name = "AirbagSides", Value = 8},
      };
    
      // call CreateDataType with baseDataTypeId = DataTypeIds.OptionSet 
      DataTypeState featuresOptionSetType = CreateDataType(DataTypeIds.OptionSet, "FeaturesOptionSetType", featuresEnum);
    

Note: Any custom data type from the above list must be created providing an EnumDefinition object as dataTypeDefinition parameter to the CreateDataType() method.

  • Custom Structure as subtype of Structure data type or any sub type of Structure data type (except Union or OptionSet). To create a custom Structure one shall use CreateDataType(<node id of super type>, <new type name>, <StructureDefinition instance>).

Note: The StructureDefinition instance used at this point shall have the StructureType field set to Structure and for all StructureFields from Fields collection the IsOptional flag set on false.

	// create a StructureDefinition object, specify IsOptional = false for all fields 
	StructureDefinition vehicleStructure = new StructureDefinition();

	// set the StructureType property to Structure
	vehicleStructure.StructureType = StructureType.Structure;
	vehicleStructure.Fields = new StructureFieldCollection()
	{
	  new StructureField(){Name = "Name", DataType = DataTypeIds.String, IsOptional = false, ValueRank = ValueRanks.Scalar},
	  new StructureField(){Name = "Owner", DataType = ownerType.NodeId, IsOptional = false, ValueRank = ValueRanks.Scalar},
	  new StructureField(){Name = "Features", DataType = featuresOptionSetType.NodeId, IsOptional = false, ValueRank = ValueRanks.Scalar},
	  new StructureField(){Name = "FuelLevel", DataType = fuelLevelDetailsType.NodeId, IsOptional = false, ValueRank = ValueRanks.Scalar},
	  new StructureField(){Name = "DisplayWarning", DataType = displayWarningType.NodeId, IsOptional = false, ValueRank = ValueRanks.Scalar},
	  new StructureField(){Name = "State", DataType = engineStateType.NodeId, IsOptional = false, ValueRank = ValueRanks.Scalar},
	};

	// call CreateDataType with baseDataTypeId = DataTypeIds.Structure 
	DataTypeState vehicleType = CreateDataType(DataTypeIds.Structure, "VehicleType", vehicleStructure);
  • Custom Structure with optional fields as subtype of Structure data type or any sub type of Structure data type (except Union or OptionSet). To create a custom Structure with optional fields one shall use CreateDataType(<node id of super type>, <new type name>, <StructureDefinition instance>).

Note: The StructureDefinition instance used at this point shall have the StructureType field set to StructureWithOptionalFields and the IsOptional flag set correctly for each StructureField in Fields collection.**

	// create a StructureDefinition object and specify which fields are optional 
	StructureDefinition ownerStructure = new StructureDefinition();
	// set the StructureType property to StructureWithOptionalFields
	ownerStructure.StructureType = StructureType.StructureWithOptionalFields;

	ownerStructure.Fields = new StructureFieldCollection()
	{
	  new StructureField(){Name = "Name", DataType = DataTypeIds.String, IsOptional = false, ValueRank = ValueRanks.Scalar},
	  new StructureField(){Name = "Age", DataType = DataTypeIds.Byte, IsOptional = true, ValueRank = ValueRanks.Scalar},
	  new StructureField(){Name = "Details", DataType = DataTypeIds.String, IsOptional = true, ValueRank = ValueRanks.Scalar},
	};

	// call CreateDataType with baseDataTypeId = DataTypeIds.Structure 
	DataTypeState ownerType = CreateDataType(DataTypeIds.Structure, "OwnerDetailsType", ownerStructure);
  • Custom Union as subtype of Union data type. To create a custom Union data type one shall use CreateDataType(DataTypeIds.Union, <new type name>, <StructureDefinition instance>).

Note: The StructureDefinition instance used at this point shall have the StructureType field set to Union.

	// create a StructureDefinition object that describes a union data type 
	StructureDefinition fuelLevelDetailsUnion = new StructureDefinition();

	// set the StructureType property to Union
	fuelLevelDetailsUnion.StructureType = StructureType.Union;
	fuelLevelDetailsUnion.Fields = new StructureFieldCollection()
	{
	  new StructureField(){Name = "IsEmpty", DataType = DataTypeIds.Boolean, ValueRank = ValueRanks.Scalar},
	  new StructureField(){Name = "IsFull", DataType = DataTypeIds.Boolean, ValueRank = ValueRanks.Scalar},
	  new StructureField(){Name = "Liters", DataType = DataTypeIds.Float, ValueRank = ValueRanks.Scalar},
	};

	// call CreateDataType with baseDataTypeId = DataTypeIds.Union 
	DataTypeState fuelLevelDetailsType = CreateDataType(DataTypeIds.Union, "FuelLevelDetailsType", fuelLevelDetailsUnion);

Note: The custom data types created with CreateDataType are described only by their DataTypeDefinition attribute and not added to data type dictionaries, therefore any client that do not know how to handle the DataTypeDefinition attribute will be unable to work with these data types.

Custom Data Type Instances

OPC UA .NET Standard SDK Server Library created a new type hierarchy for handling instances of custom data types.

For creating instances of these types on server side it is recommended to use the GetDefaultValueForDatatype() method from NodeManager class.* It has the following signature:

public object GetDefaultValueForDatatype(NodeId dataType, int valueRank = ValueRanks.Scalar, int arrayLength = 0, int[] arrayDimensions = null)

Note: When the valueRank parameter is not Scalar or OneDimesion the GetDefaultValueForDatatype() method will return a Matrix object.

The object returned by GetDefaultValueForDatatype() method can be set as variable node value and because BaseComplexTypeValue implement IEncodeable and all the derived types implement Encode() and Decode() methods the server will be able to work with this custom data type instances transparently.

Note: The runtime type of the return value of GetDefaultValueForDatatype will be an integer type in case of EnumValue because the OPC UA Specification requires that in Enumeration nodes the value shall be of an integer type.

// create a variable with DataType = custom data type
var vehicle1Variable = CreateVariable(m\_rootCustomTypesFolder, "Vehicle", vehicleType.NodeId);

// get the default value assigned by CreateVariable method. In this case it is of StructuredValue type
StructuredValue vehicle = vehicle1Variable.Value as StructuredValue;

if (vehicle != null)
{
  // change the value of one field. The change will be visible in the vehicle1Variable node because its own value object is being edited.
  vehicle["Name"] = "BMW";
}

Note: Using the diagram from above determine and use the right type of custom data type instance in order to be able to change the specific properties.

More examples of how to change properties of custom data type instances are available in SampleClient, ReadWriteClient.cs class.

Custom Variable Types

The NodeManager class from OPC UA .NET Standard SDK Server Library provides a method to create a custom variable type called CreateVariableType():

public BaseVariableTypeState CreateVariableType(NodeId baseVariableTypeId, string name, NodeId dataType = null, int valueRank = ValueRanks.Scalar, bool isAbstract = false)

CreateVariableType() method creates a new BaseVariableTypeState object with the specified name, sets its SuperTypeId to the specified baseVariableTypeId.

If dataType parameter is not provided then the new variable type will use DataTypeIds.BaseDataType for the DataType attribute of new BaseVariableTypeState object.

New variable type node can have child properties or variables and they can be added using NodeManager.CreateProperty() or NodeManager.CreateVariable() methods but it is important to set the ModellingRuleId property if those properties are intended to be added to the instances of the variable type.

The following code demonstrates how to add a property to a newly created variable type and how to assign the Mandatory modelling rule:

// Create a variable type that has data type = complex data vehicle type. Its base type is customVariableType.NodeId but it can be any already existing variable type id

BaseVariableTypeState vehicleVariableType = CreateVariableType(customVariableType.NodeId, "VehicleVariableType", vehicleType.NodeId, ValueRanks.Scalar);

vehicleVariableType.Description = "Custom Variable type with DataType=VehicleType";

// create a property whose parent is the new variable type
PropertyState propertyState1 = CreateProperty(vehicleVariableType, "MandatoryBoolProperty", DataTypeIds.Boolean);

// for properties that need to be created on instances of type the modelling rule has to be specified

propertyState1.ModellingRuleId = Objects.ModellingRule_Mandatory;

Note: Use the NodeManager.CreateVariableFromType() method to create an instance of a custom variable type. The following code demonstrates this:

var vehicleVariable = CreateVariableFromType(m_rootCustomTypesFolder, "VehicleVariableInstance", vehicleVariableType.NodeId, ReferenceTypeIds.HasComponent);

vehicleVariable.Description = "Variable instance of custom VariableType: VehicleVariableType ";

Custom Object Types

The NodeManager class from OPC UA .NET Standard SDK Server Library provides a method to create a custom object type called CreateVariableType():

public BaseObjectTypeState CreateObjectType(NodeId baseObjectTypeId, string name, bool isAbstract = false)

CreateObjectType() method creates a new BaseObjectTypeState object with the specified name, sets its SuperTypeId to the specified baseObjectTypeId.

If isAbstract parameter is not provided then the new object type will use have its IsAbstract attribute set on false.

New object type node can have child nodes (properties, variables objects or methods) and they can be added using the dedicated methods from NodeManager but it is important to set the ModellingRuleId property if those nodes are intended to be added to the instances of the object type.

The following code demonstrates how to add a node to a newly created object type and how to assign the Mandatory modelling rule:

// Create an object type that has ObjectTypeIds.BaseObjectType as its base type. An object type can be created using any already existing object type id

BaseObjectTypeState customObjectType = CreateObjectType(ObjectTypeIds.BaseObjectType, "CustomObjectType", true);
customObjectType.Description = "Custom abstract object Type with one mandatory property";

// create a property whose parent is the new object type
PropertyState propertyState = CreateProperty(customObjectType, "MandatoryFloatProperty", DataTypeIds.Float);

// for properties that need to be created on instances of type the modelling rule has to be specified

propertyState.ModellingRuleId = Objects.ModellingRule_Mandatory;

Note: Use the NodeManager.CreateObjectFromType() method to create an instance of a custom object type. The following code demonstrates this:

// create instance form new object type
var parkingLotInstance = CreateObjectFromType(m_rootCustomTypesFolder, "ParkingLotInstance", parkingObjectType.NodeId, ReferenceTypeIds.Organizes);

parkingLotInstance.Description = "Object instance of custom ObjectType: ParkingObjectType ";

MethodState addVehicleMethodInstance = parkingLotInstance.FindChild(SystemContext, addVehicleMethod.BrowseName) as MethodState;

if (addVehicleMethodInstance != null)
{
  // Add event handler for object instance method call
  addVehicleMethodInstance.OnCallMethod = ParkingLotAddVehicleOnCallHandler;
}

Custom Event Types

The event types are a special kind of object types that are derived from ObjectTypeIds.BaseEventType.

The Custom Object Types section describes the creation of object types.

The following code will create a custom event type.

Any object type derived from ObjectTypeIds.BaseEventType or a subtype of it will be treated as an event type:

// create custom event type BaseObjectTypeState customEventType = CreateObjectType(ObjectTypeIds.BaseEventType, "CustomEventType", false);

//remember CustomEventType id m_customEventTypeNodeId = customEventType.NodeId;

customEventType.Description = "Custom EventType with some custom properties";
propertyState = CreateProperty(customEventType, "CountProperty", DataTypeIds.Int32);
// for properties that need to be created on instances of type the modelling rule has to be specified

propertyState.ModellingRuleId = Objects.ModellingRule_Mandatory;

propertyState = CreateProperty(customEventType, "RandomValueProperty", DataTypeIds.Int32);

// for properties that need to be created on instances of type the modelling rule has to be specified

propertyState.ModellingRuleId = Objects.ModellingRule_Mandatory;

Generate Events

Events represent specific transient occurrences, for example system configuration changes or system errors. Event Notifications report the occurrence of an Event. Objects and Views can be used to subscribe to Events. The EventNotifier Attribute of those Nodes identifies if the Node allows subscribing to Events. Clients subscribe to such Nodes to receive Notifications of Event occurrences.

Event Subscriptions use the Monitoring and Subscription Services to subscribe to Event Notifications of a node. Any OPC UA Server that supports events shall expose at least one node as EventNotifier. The Server Object is used for this purpose. Events generated by the server are available via this Server Object. A server is not expected to produce events if the connection to the event source is down for some reason.

Events may also be exposed through other nodes anywhere in the address space. These nodes (identified via the EventNotifier Attribute) provide some subset of the Events generated by the Server. The position in the address space dictates what this subset will be. For example, a process area Object representing a functional area of the process would provide Events originating from that area of the process only. It should be noted that this is only an example and it is fully up to the server to determine what events should be provided by which node.

Expose Event Notifiers

Event notifiers can be exposed in the address space using OPC UA .NET Standard SDK Server Library. If you defined some object instances inside CreateAddressSpace() method of your node manager class, the event notifier can be set on either instance like in the example below:

//Enable subscriptions
root.EventNotifier = EventNotifiers.SubscribeToEvents;
//creating notifier ensures events propagate up the hierarchy when are produced
AddRootNotifier(root);
//add link to server object
AddReference(root, ReferenceTypeIds.Organizes, true, ObjectIds.ObjectsFolder, true);
//add sub-notifiers
instance.EventNotifier = EventNotifiers.SubscribeToEvents;

AddNotifier(root, instance, true);

AddNotifier() method, defined in NodeManager class, adds a notifier relationship between source and target. It adds HasNotifier ReferenceType between source node and target node specified as parameters. HasNotifier ReferenceType is used to establish a hierarchical organization of event notifying Objects. It is a subtype of HasEventSource. The SourceNode of this ReferenceType shall be an Object or View that is a source of event subscriptions. The TargetNode of this ReferenceType shall be an Object that is a source of event subscriptions. A node that is source of event subscriptions is an Object that has its “SubscribeToEvents” bit set within the EventNotifier Attribute and allows clients to make an Event Monitor Item for the that node.

The hierarchical organization of HasNotifiers is formed by chains that are helping events to be propagated from children to their parents. If a node in this chain is missing his HasNotifier reference the event will be not propagated any more.

Raise events

Each Event is of a specific EventType. A server may support many types. There is a BaseEventType that all other EventTypes derive from. The EventTypes supported by a server are exposed in the address space. EventTypes are represented as ObjectTypes in the address space and do not have a special NodeClass associated to them.

The default implementation from NodeManager class provides a ReportEvent() method that raises an event that is a snapshot of the eventState parameter.

This is the ReportEvent method signature:

public virtual void ReportEvent(NodeState sourceNode, BaseEventState eventState, LocalizedText eventMessage = null, EventSeverity eventSeverity = 0)

  • If the sourceNode parameter is null the Server node will be used.

  • If the eventState parameter is null the method will throw an exception.

  • If the eventMessage parameter is not provided the Message property of the eventState parameter will be used.

  • If the eventSeverity parameter is not provided the Severity property of the eventState parameter will be used.

The DoSimulation() method from DataAccess Node Manager uses ReportEvent() to raise notification when m_motorTemperature value changes.

// Report an event at on DataAccess node
string eventMessage = String.Format("Motor temperature changed to {0}", m_motorTemperature.Value);

ReportEvent(m\_motorTemperature, m_motorTemperatureEvent, new LocalizedText(eventMessage), EventSeverity.Medium);

The m_motorTemperatureEvent object used in the code snippet from above is an instance of BaseEventState and it is created at path: DataAccess\Refrigerator\MotorTemperature\MotorTemperatureEvent using the following code:

// Create a root node and add a reference to external Server Objects Folder
FolderState root = CreateObjectFromType(null, "DataAccess", ObjectTypeIds.FolderType, ReferenceTypeIds.Organizes) as FolderState;
AddReference(root, ReferenceTypeIds.Organizes, true, ObjectIds.ObjectsFolder, true);

// Enable DataAccess folder to subscribe to events
root.EventNotifier |= EventNotifiers.SubscribeToEvents;

// Add root notifier for DataAccess folder
AddRootNotifier(root);
…

// Create Refrigerator object
BaseObjectState refrigerator = CreateObject(root, "Refrigerator");

// Enable Refrigerator object to subscribe to events
refrigerator.EventNotifier |= EventNotifiers.SubscribeToEvents;

…

// Create MotorTemperature variable
m_motorTemperature = CreateAnalogVariable(refrigerator, "MotorTemperature", DataTypeIds.Double, ValueRanks.Scalar, new Range(90, 10), null);

…

// create an instance of BaseEventType to be used when reporting MotorTemperature events
m_motorTemperatureEvent = CreateObjectFromType(m_motorTemperature, "MotorTemperatureEvent", ObjectTypeIds.BaseEventType) as BaseEventState;

The following code provides a sample of raising a different type of event, a custom event type. It uses the m_customEventInstance an event created for custom event type m_customEventTypeNodeId.

private ServiceResult RaiseCustomEventOnCallHandler(ISystemContext context, MethodState method, IList<object> inputArguments, IList<object> outputArguments)
{
	if (m_customEventInstance != null)
	{
	 // Set custom properties
	 var countProperty = m_customEventInstance.FindChildBySymbolicName(SystemContext, "CountProperty") as BaseVariableState;
	 if (countProperty != null)
	 {
	  countProperty.Value = ++m_customEventCounter;
	 }

	 var randomProperty = m_customEventInstance.FindChildBySymbolicName(SystemContext, "RandomValueProperty") as BaseVariableState;
	 if (randomProperty != null)
	 {
	  randomProperty.Value = new Random(100).Next();
	 }

	 LocalizedText eventMessage = new LocalizedText("CustomEvent" + m_customEventCounter);

	 **ReportEvent(m_rootCustomTypesFolder, m_customEventInstance, eventMessage, EventSeverity.Medium);**

	 return new ServiceResult(StatusCodes.Good);
	}

	return new ServiceResult(StatusCodes.BadNodeIdInvalid);
}

Raising an event can also be done using OPC UA .NET Standard SDK Server Library by calling Server.ReportEvent(e). For example we can raise an AuditUpdateMethodEvent inside a method call by invoking:

RaiseAuditUpdateMethodEvent(context, method, inputArguments);

Where RaiseAuditUpdateMethodEvent is defined as below:

/// <summary>
/// Raise the AuditUpdateMethodEventState for a method
/// </summary>
/// <param name="context"></param>
/// <param name="method"></param>
/// <param name="inputArguments"></param>
private void RaiseAuditUpdateMethodEvent(ISystemContext context, MethodState method, IList<object> inputArguments)
{

  BaseObjectState parent = method.Parent as BaseObjectState;

  if (parent != null)
  {
     // create new instance AuditUpdateMethodEventState.
     AuditUpdateMethodEventState auditUpdateMethodEventInstance = new AuditUpdateMethodEventState(null);
     auditUpdateMethodEventInstance.Initialize(SystemContext, null, EventSeverity.Medium, new LocalizedText(eventMessage));

     // fill in additional fields.
     auditUpdateMethodEventInstance.SetChildValue(context, BrowseNames.SourceNode, parent.NodeId, false);
     auditUpdateMethodEventInstance.SetChildValue(context, BrowseNames.SourceName, "Attribute/Call", false);
     auditUpdateMethodEventInstance.SetChildValue(context, BrowseNames.ServerId, context.ServerUris.GetString(0), false);
     auditUpdateMethodEventInstance.SetChildValue(context, BrowseNames.ClientAuditEntryId, context.AuditEntryId, false);

     // Set event data
     auditUpdateMethodEventInstance.SetChildValue(SystemContext, BrowseNames.EventId, Guid.NewGuid().ToByteArray(), false);
     auditUpdateMethodEventInstance.SetChildValue(SystemContext, BrowseNames.LocalTime, Utils.GetTimeZoneInfo(), false);

     var time = DateTime.UtcNow;
     auditUpdateMethodEventInstance.SetChildValue(SystemContext, BrowseNames.Time, time, false);
     auditUpdateMethodEventInstance.SetChildValue(SystemContext, BrowseNames.ReceiveTime, time, false);      

     if (context.UserIdentity != null)
     {
       auditUpdateMethodEventInstance.SetChildValue(context, BrowseNames.ClientUserId, context.UserIdentity.DisplayName, false);
     }

     auditUpdateMethodEventInstance.SetChildValue(context, BrowseNames.MethodId, method.NodeId, false);

     // need to change data type.
     Variant[] values = new Variant[inputArguments.Count];

     for (int ii = 0; ii < values.Length; ii++)
     {
       values[ii] = new Variant(inputArguments[ii]);
     }

     auditUpdateMethodEventInstance.SetChildValue(context, BrowseNames.InputArguments, values, false);      
    
     // report the event to the server object.
     **Server.ReportEvent(auditUpdateMethodEventInstance);**
  }

}

Filter events

Events can be filtered by a SelectClause and by a WhereClause. SelectClause contains a list of values to return with each event in a notification. WhereClause limits the notifications to those events that match the criteria defined.

The Event Filters can be defined by client for an Event Monitored Item. For your server you don't need to write code for filtering as all the logic needed is already provided by OPC UA .NET Standard SDK Server Library.

Alarms and Conditions

Events

Alarms and Conditions are based on Events.

An event is a notification of an occurrence within a system or system component. An event represents a notification of a transient occurrence (i.e. stateless) - these events are also called "simple events".

In case of non-transient events (i.e. stateful occurrences) the events are named conditions or alarms. The conditions and alarms are usually represented in the address space of an OPC UA server.

The BaseEventType defines the common properties of all derived simple events and condition types.

The common properties of all events are listed in the table below:

|Name |OPC UA DataType |Description | | - | - | - | |EventId |ByteString |Generated by the OPC UA Stack when reporting an event. | |EventType |NodeId |Describes the type of the event. It is the node ID of the type node. | |SourceNode |NodeId |Node which originates the event. | |SourceName |String |Description of the source of the event. It could be the display name of the event source. | |Time |UtcTime |The time when the event occurs. This value is set as close to the event generator as possible. | |ReceiveTime |UtcTime |The time when the OPC UA server receives the event. | |LocalTime |TimeZoneDataType |Optional field. Time zone information of the location where the event occurred. | |Message |LocalizedText |A human readable description of the event. | |Severity |UInt16 |The urgency (priority) of the event. Values are allowed between 1 (low) and 1000 (high). | The EventId is used to identify a specific state of an event or condition. Since simple events are transient the client cannot use the EventId at all - no persistent object in the server is available. Simple events are not represented in the address space. Instances of simple events are created for reporting only and were destructed after reporting.

For stateful conditions however the EventId is used to uniquely identify a specific condition in a specific state. Every change in the condition state shall be reported to the client - and with every notification sent to the client a new unique EventId is generated. Since the client has to use this unique EventId for any requests on a condition it is ensured that the request does not act on an old state.

The Alarm and Condition model extends the OPC UA base Event model by defining various event types based on the BaseEventType. All of the event types defined in this standard can be further extended to form domain or server specific alarm and condition types.

Condition Model

The condition model extends the event model by defining the ConditionType. The ConditionType introduces the concept of states differentiating it from the base event model.

Most common ConditionType properties

Name OPC UA DataType Description
BranchId NodeId Null for all Event Notifications that relate to the current state of the Condition instance. If BranchId is not Null it identifies a previous state of this Condition instance that still needs attention by an Operator.
Retain Boolean When TRUE describes a Condition (a ConditionBranch) as being in a state that is interesting for a Client wishing to synchronize its state with the Server’s state.
ClientUserId String The identity of the user who inserted the most recent Comment.

Most common ConditionType variables

Name OPC UA DataType Description
EnabledState LocalizedText Indicates whether the Condition is enabled. EnabledState/Id is TRUE if enabled, FALSE otherwise.
LastSeverity UInt16 Indicates the urgency of the Condition and is also commonly called ‘priority’, especially in relation to process Alarms
Comment LocalizedText User generated string that is to be associated with a certain state of a Condition.
Quality StatusCode Refers to the quality of the data value(s) upon which this Condition is based.

Most common ConditionType methods

Name Description
Disable Method Used to change a Condition instance to the disabled state. The MethodId passed to the Call Service is found by browsing the Condition instance in the AddressSpace.
Enable Method Used to change a Condition instance to the enabled state. The MethodId passed to the Call Service is found by browsing the Condition instance in the AddressSpace.
AddComment Method Used to apply a comment to a specific state of a Condition instance. Comments are added to Event occurrences identified via an EventId. EventIds where the related EventType does not support Comments at all are rejected.
ConditionRefresh Method Allows a Client to request a Refresh of all Condition instances that currently are in an interesting state (they have the Retain flag set). This includes previous states of a Condition instance for which the Server maintains Branches.
Acknowledge Method Used to acknowledge an Event Notification for a Condition instance state where AckedState was set to false. A valid EventId will result in an Event Notification where AckedState/Id is set to true and the Comment Property contains the text of the optional comment argument.
Confirm Method Used to confirm an Event Notifications for a Condition instance state where ConfirmedState was set to false. A valid EventId will result in an Event Notification where ConfirmedState/Id is set to true and the Comment Property contains the text of the optional comment argument.

ExclusiveLimitAlarmType

The ExclusiveLimitAlarmType is used to specify the common behaviour for alarm types with multiple mutually exclusive limits.

The ExclusiveLimitStateMachineType defines the state machine used by alarm types that handle multiple mutually exclusive limits.

Historical Access

The OPC UA Historical Access specification defines of the handling of historical time series data and historical event data in the OPC Unified Architecture.

An OPC UA Server supporting historical access provides one or more OPC UA Client with transparent access to different historical data and/or historical event sources. The historical data or events may be located in a proprietary data collection, database or a short term buffer within memory.

An OPC UA Server supporting historical access may or may not provide historical data and events for some or all available variables, objects, properties or views within the server address space. As with the other information models, the address space of an OPC UA Server supporting historical access is accessed via the View or Query Service sets. An OPC UA Server supporting historical access provides a way to access or communicate to a set of historical data and/or historical event sources. The types of sources available are a function of the server implementation.

Expose historical data

OPC UA .NET Standard SDK provides samples that exemplify the management of the historian nodes. Both historical data and historical events are managed by sample servers. Lookup for the following samples:

  • HistoricalDataAccess Node Manager - for exposing historical data in your server.

Note: The OPC UA .NET Standard Stack from OPC Foundation implements all 37 aggregate functions defined by OPC UA Specification. The list from below contains the supported aggregate function Ids that can be used when creating a HistoryReadRequest. History Read Processed chapter contains an example of using these Ids.

  1. ObjectIds.AggregateFunction_Interpolative

  2. ObjectIds.AggregateFunction_Average

  3. ObjectIds.AggregateFunction_TimeAverage

  4. ObjectIds.AggregateFunction_TimeAverage2

  5. ObjectIds.AggregateFunction_Total

  6. ObjectIds.AggregateFunction_Total2

  7. ObjectIds.AggregateFunction_Minimum

  8. ObjectIds.AggregateFunction_Maximum

  9. ObjectIds.AggregateFunction_MinimumActualTime

  10. ObjectIds.AggregateFunction_MaximumActualTime

  11. ObjectIds.AggregateFunction_Range

  12. ObjectIds.AggregateFunction_Minimum2

  13. ObjectIds.AggregateFunction_Maximum2

  14. ObjectIds.AggregateFunction_MinimumActualTime2

  15. ObjectIds.AggregateFunction_MaximumActualTime2

  16. ObjectIds.AggregateFunction_Range2

  17. ObjectIds.AggregateFunction_Count

  18. ObjectIds.AggregateFunction_AnnotationCount

  19. ObjectIds.AggregateFunction_DurationInStateZero

  20. ObjectIds.AggregateFunction_DurationInStateNonZero

  21. ObjectIds.AggregateFunction_NumberOfTransitions

  22. ObjectIds.AggregateFunction_Start

  23. ObjectIds.AggregateFunction_End

  24. ObjectIds.AggregateFunction_Delta

  25. ObjectIds.AggregateFunction_StartBound

  26. ObjectIds.AggregateFunction_EndBound

  27. ObjectIds.AggregateFunction_DeltaBounds

  28. ObjectIds.AggregateFunction_DurationGood

  29. ObjectIds.AggregateFunction_DurationBad

  30. ObjectIds.AggregateFunction_PercentGood

  31. ObjectIds.AggregateFunction_PercentBad

  32. ObjectIds.AggregateFunction_WorstQuality

  33. ObjectIds.AggregateFunction_WorstQuality2

  34. ObjectIds.AggregateFunction_StandardDeviationPopulation

  35. ObjectIds.AggregateFunction_VariancePopulation

  36. ObjectIds.AggregateFunction_StandardDeviationSample

  37. ObjectIds.AggregateFunction_VarianceSample

User Authentication

The OPC UA .NET Standard SDK Server Library provides support for handling user authentication in UaServer* class. You can customize user authentication by overriding ValidateUserPassword(), ValidateSystemConfigurationIdentity(), ValidateUserCertificate() and ValidateIssuedIdentity() methods. For samples of ValidateUserPassword() and ValidateSystemConfigurationIdentity() overrides please read SampleServer Class.

User Authorization

Server applications can determine in their own way what data is accessible and what operations are authorized.

The servers implemented using OPC UA .NET Standard SDK Server Library can support the User Authorization Information Model defined in OPC UA specification Parts 3 and 5. It includes support for Roles, Role Permissions, User Role Permissions and Access Restrictions.

Access Restrictions

The Access Restrictions regulate Clients access to Server nodes based on the secure channel properties.

  • The Access Restrictions can be defined at namespace level. If a Server supports AccessRestrictions for a particular Namespace it adds the DefaultAccessRestrictions Property to the NamespaceMetadata Object for that Namespace.

  • Each node from an OPC UA Server has an optional attribute called AccessRestrictions and, if set, it overrides the namespace DefaultAccessRestrictions setting.* Both, AccessRestrictions attribute of base NodeClass and DefaultAccessRestrictions Property value of the NamespaceMetadata Object, are of type AccessRestrictionsType and they have the following possible values:

Name Bit Description
None No restriction applied for Clients.
SigningRequired 0 The Client can only access the Node when using a SecureChannel which digitally signs all messages.
EncryptionRequired 1 The Client can only access the Node when using a SecureChannel which encrypts all messages.
SessionRequired 2 The Client cannot access the Node outside of the scope of a session (e.g. when using SessionlessInvoke Service invocation).

Secure channel

Roles

OPC UA defines a standard approach for implementing role based security. The OPC UA approach assigns Permissions to Roles for each Node in the AddressSpace (Role Permissions and User Role Permissions). Clients are then granted Roles when they create a Session based on the information provided by the Client.

In OPC UA Specification a Role is defined as "a function assumed by a Client when it accesses a Server".

Roles are used to separate authentication (determining who a Client is) from authorization (determining what the Client is allowed to do). By separating these tasks Servers can allow centralized services to manage user identities and credentials while the Server only manages the Permissions on its Nodes assigned to Roles.

The set of Roles supported by a Server are published as components of the Roles Object defined in Part 5 from OPC UA Specification.

OPC UA .NET Standard SDK Server Library does not support changing the RoleSet collection by calling the AddRole or RemoveRole methods of the RoleSetState object. These methods will return BadNotSupported status code.

The following table lists the default 'well known' roles provided by OPC UA .NET Standard Stack from OPC Foundation and their NodeIds.

Role Node Id Details
Anonymous ObjectIds.WellKnownRole_Anonymous <p>Session has anonymous credentials. </p><p>OPC UA .NET Standard SDK Server Library adds the Anonymous identity to Anonymous role. This means that, by default, the anonymous user identity will have the Anonymous role in its GrantedRoles collection.</p>
AuthenticatedUser ObjectIds.WellKnownRole_AuthenticatedUser <p>Any user with valid credentials.</p><p>OPC UA .NET Standard SDK Server Library adds the AuthenticatedUser identity to AuthenticatedUser role. This means that, by default, any user with valid credentials will have the AuthenticatedUser role in its GrantedRoles collection.</p>
ConfigureAdmin ObjectIds.WellKnownRole_ConfigureAdmin <p>All well known roles shall be customized by the server implementation by overriding OnRoleSetInitialized method.</p><p> </p><p>Note: If the server does no customization these roles are ignored by the authorization process because it is mandatory that each role has at least one identity mapping in its Identities property.</p>
Engineer ObjectIds.WellKnownRole_Engineer
Observer ObjectIds.WellKnownRole_Observer
Operator ObjectIds.WellKnownRole_Operator
SecurityAdmin ObjectIds.WellKnownRole_SecurityAdmin
Supervisor ObjectIds.WellKnownRole_Supervisor

When a Session is created, the Server determines what Roles are granted to that Session and they are available in on the server side in the IUserIdentity.GrantedRoles property available on each server session after authentication.

The standard mapping rules allow Roles to be granted based on:

User identity - based on user names, user certificates or user groups. Well known groups include ‘AuthenticatedUser’ (any user with valid credentials) and ‘Anonymous’ (no user credentials provided).

Application identity - based on the ApplicationUri specified in the Client Certificate. Application identity can only be enforced if the Client proves possession of a trusted Certificate by using it to create a Secure Channel or by providing a signature in ActivateSession (see Part 4).

Endpoint - based on the URL used to connect to the Server. Endpoint identity can be used to restrict access to Clients running on particular networks.

Role Permissions

The Role Permissions specifies the Permissions that apply to a Node for all Roles which have access to the Node.

In an OPC UA Server:

  • The Role Permissions can be defined at namespace level. If a Server supports RolePermissions for a particular Namespace it adds the DefaultRolePermissions Property to the NamespaceMetadata Object for that Namespace.

  • Each node from an OPC UA Server has an optional attribute called RolePermissions and, if set, it overrides the namespace DefaultRolePermissions setting. Both, RolePermissions attribute of base NodeClass and DefaultRolePermissions Property value of the NamespaceMetadata Object, are defined as an array of RolePermissionType.

|RolePermissionType|Structure|Specifies the Permissions for a Role| | :- | :- | :- | | RoleId |NodeId|The NodeId of the Role Object.| | Permissions|PermissionType|A mask specifying which Permissions are available to the Role.| The PermissionType is a OPC UA OptionSet enumeration type and this is how it is described in OPC UA Specification part 3:

|Name |Bit |Description | | :- | :- | :- | |Browse |0 |<p>The Client is allowed to see the references to and from the Node. </p><p>The Client is able to Read to Attributes other than the Value or the RolePermissions Attribute. </p><p>This Permission is valid for all NodeClasses. </p>| |ReadRolePermissions |1 |<p>The Client is allowed to read the RolePermissions Attribute. </p><p>This Permission is valid for all NodeClasses. </p>| |WriteAttribute |2 |<p>The Client is allowed to write to Attributes other than the Value, Historizing or RolePermissions Attribute if the WriteMask indicates that the Attribute is writeable. </p><p>This bit affects the value of a UserWriteMask Attribute. </p><p>This Permission is valid for all NodeClasses. </p>| |WriteRolePermissions |3 |<p>The Client is allowed to write to the RolePermissions Attribute if the WriteMask indicates that the Attribute is writeable. </p><p>This bit affects the value of the UserWriteMask Attribute. </p><p>This Permission is valid for all NodeClasses. </p>| |WriteHistorizing |4 |<p>The Client is allowed to write to the Historizing Attributes if the WriteMask indicates that the Attribute is writeable. </p><p>This bit affects the value of the UserWriteMask Attribute. </p><p>This Permission is only valid for Variables. </p>| |Read |5 |<p>The Client is allowed to read the Value Attribute. </p><p>This bit affects the CurrentRead bit of the UserAccessLevel Attribute. </p><p>This Permission is only valid for Variables. </p>| |Write |6 |<p>The Client is allowed to write the Value Attribute. </p><p>This bit affects the CurrentWrite bit of the UserAccessLevel Attribute. </p><p>This Permission is only valid for Variables. </p>| |ReadHistory|7|<p>The Client is allowed to read the history associated with a Node. </p><p>This bit affects the HistoryRead bit of the UserAccessLevel Attribute. </p><p>This Permission is only valid for Variables, Objects or Views. </p>| |InsertHistory|8|<p>The Client is allowed to insert the history associated with a Node. </p><p>This bit affects the HistoryWrite bit of the UserAccessLevel Attribute. </p><p>This Permission is only valid for Variables, Objects or Views. </p>| |ModifyHistory|9|<p>The Client is allowed to modify the history associated with a Node. </p><p>This bit affects the HistoryWrite bit of the UserAccessLevel Attribute. </p><p>This Permission is only valid for Variables, Objects or Views. </p>| |DeleteHistory|10|<p>The Client is allowed to delete the history associated with a Node. </p><p>This bit affects the HistoryWrite bit of the UserAccessLevel Attribute. </p><p>This Permission is only valid for Variables, Objects or Views. </p>| |ReceiveEvents|11|<p>A Client only receives an Event if this bit is set on the Node identified by the EventTypeId field and on the Node identified by the SourceNode field. </p><p>This Permission is only valid for EventType Nodes or SourceNodes. </p>| |Call|12|<p>The Client is allowed to call the Method if this bit is set on the Object or ObjectType Node passed in the Call request and the Method Instance associated with that Object or ObjectType. </p><p>This bit affects the UserExecutable Attribute when set on Method Node. </p><p>This Permission is only valid for Objects, ObjectType or Methods. </p>| |AddReference|13|<p>The Client is allowed to add references to the Node. </p><p>This Permission is valid for all NodeClasses. </p>| |RemoveReference|14|<p>The Client is allowed to remove references from the Node. </p><p>This Permission is valid for all NodeClasses. </p>| |DeleteNode|15|<p>The Client is allowed to delete the Node. </p><p>This Permission is valid for all NodeClasses. </p>| |AddNode|16|<p>The Client is allowed to add Nodes to the Namespace. </p><p>This Permission is only used in the DefaultRolePermissions and DefaultUserRolePermissions Properties of a NamespaceMetadata Object </p>| Role Permissions chapter from SampleServer section provides sample code for setting Role Permissions.

User Role Permissions

User Role Permissions specifies the Permissions that apply to a Node for all Roles granted to current Session.

In an OPC UA Server:

  • The User Role Permissions can be defined at namespace level. If a Server supports UserRolePermissions for a particular Namespace it adds the DefaultUserRolePermissions Property to the NamespaceMetadata Object for that Namespace. It is recommended to implement the OnReadValue handler of DefaultUserRolePermissions Property to provide the default user role permissions for a specific namespace.

  • Each node from an OPC UA Server has an optional attribute called UserRolePermissions and, if set, it overrides the namespace DefaultUserRolePermissions setting.* The UserRolePermissions attribute shall not be writable, it is recommended to implement the OnReadUserRolePermissions handler to provide the user role permissions for a specific node. UserRolePermissions attribute of base NodeClass and DefaultUserRolePermissions Property value of the NamespaceMetadata Object, are defined as an array of RolePermissionType. RolePermissionType is described in Role Permissions chapter.

User Request Authorization

User Authorization for all requests

In a more general approach, the user can authorize all the service requests with a common application specific logic.

In the sample below, by overriding the ValidateRequest method of the UaServer, all write requests of the anonymous connections are rejected.

protected override OperationContext ValidateRequest(RequestHeader requestHeader, RequestType requestType)
{

  OperationContext context = base.ValidateRequest(requestHeader, requestType);

  if (requestType == RequestType.Write)
  {
     // reject all writes if no user provided.
     if (context.UserIdentity.TokenType == UserTokenType.Anonymous)
     {

       // construct translation object with default text.
       TranslationInfo info = new TranslationInfo(
              "NoWriteAllowed",
              "en-US",
              "Must provide a valid windows user before calling write.");

              // create an exception with a vendor defined sub-code.
              throw new ServiceResultException(new ServiceResult(
                  StatusCodes.BadUserAccessDenied,
                  "NoWriteAllowed",
                  Namespaces.UserAuthentication,
                  new LocalizedText(info)));
     }
  }

  return context;
}

WriteMask, AccessLevel, Executable

WriteMask and UserWriteMask

The optional WriteMask Attribute exposes the possibilities of a client to write the Attributes of the Node. The WriteMask Attribute does not take any user access rights into account, that is, although an Attribute is writeable this may be restricted to a certain user/user group.

The optional UserWriteMask Attribute exposes the possibilities of a client to write the Attributes of the Node taking user access rights into account. The UserWriteMask Attribute can only further restrict the WriteMask Attribute, when it is set to not writable in the general case that applies for every user.

AccessLevel and UserAccessLevel

The AccessLevel Attribute is specific to Variables and it is used to indicate how the Value of a Variable can be accessed (read/write) and if it contains current and/or historic data. The AccessLevel does not take any user access rights into account, i.e. although the Variable is writable this may be restricted to a certain user/user group.

The UserAccessLevel Attribute is used to indicate how the Value of a Variable can be accessed (read/write) and if it contains current or historic data taking user access rights into account.

It is possible to specify special authorization rules for read or write on a specific node in the address space by handling OnSimpleWriteValue and OnReadUserAccessLevel events.

Please see UserAuthentication Node Manager for an example.

Executable and UserExecutable

The Executable Attribute indicates whether the Method is executable, not taking user access rights into account.

The UserExecutable Attribute indicates whether the Method is executable, taking user access rights into account.

Client Development

OPC UA .NET Standard SDK Client Library provides an easy to use API for developing OPC UA client applications.

The SampleClient application can be used as starting point for your own OPC UA client development.

Before Starting

Prerequisites:

  • C# programming knowledge.

  • OPC UA communication standard knowledge.

  • Microsoft Visual Studio 2019 with .NET 4.6.2 and .NET Core SDK 3.1.

  • OPC UA .NET Standard SDK is installed.

Sample applications

We provide a pair of sample applications, a server and a client. SampleClient connects to SampleServer and provides sample code for discovery, connect, reverse connect, browse, data access, event monitored item, handling alarms, method call, history read.

To run the samples:

  • Open Microsoft Visual Studio 2019 environment.

  • Open Sample Solution SampleApplications.sln.

  • Start SampleServer application.

  • Start SampleClient application - interact and/or debug.

To create a new OPC UA Client application:

  • Open Microsoft Visual Studio 2019 environment.

  • Create a new project and give it a name.

  • Add a reference to Softing.Opc.Ua.Client.dll assembly found in the subfolder OpcUaNetStandardToolkit\v3.00\bin\netstandard2.1.

  • Add a reference to the NuGet package OPCFoundation.NetStandard.Opc.Ua - version 1.4.367.42. (Or add reference to the fine grained OPC Foundation NuGet packages).

  • Configure the application (see Client Configuration).

Programmer’s Guide

The subsequent documentation is assuming you are building your UA clients using the OPC UA .NET Standard SDK Client Library.

It will not enter in details about UA client programming using the OPC UA .NET Standard Stack from OPC Foundation.

UaApplication Class

The UaApplication class is the root element of our OPC UA .NET Standard SDK Client Library implementation.

Every OPC UA Client application can instantiate at least one UaApplication object.

The UaApplication instances are created using 2 static Create methods from UaApplication class:

  • Create UaApplication from configuration file path: public static async Task<UaApplication> Create(string configFilePath)

  • Create UaApplication from configuration object (instance of Application Configuration class): public static async Task<UaApplication> Create(ApplicationConfiguration applicationConfiguration)

  • Create UaApplication from the fluent API configuration object (instance of ApplicationConfigurationBuilderEx) public static async Task<UaApplication> Create(ApplicationConfigurationBuilderEx applicationConfigurationBuilder)

The UaApplication class has the following properties which are readonly:

  • ApplicationInstance - Gets a reference to Opc.Ua.Configuration.ApplicationInstance underlying object.

  • Configuration - Gets a reference to the configuration object (Application Configuration) used to create the UaApplication instance.

  • ClientToolkitConfiguration - Gets a reference to current ClientToolkitConfiguration object.

-GdsConnectionConfiguration - Gets a reference to current GdsConnectionConfiguration object.

  • CurrentSessions - Gets the readonly list of ClientSession objects associated with this instance. When a new session is created using CreateSession() method it is added to this collection and when the session is closed, it is removed from this collection. The UaApplication class has the following methods:

  • ActivateLicense - Activates the Binary License using a license key.

  • CreateSession - Creates a new ClientSession to the provided url parameter. More details are provided in Connect section.

  • CreateReverseConnectSession - Creates a new ClientSession using the Reverse Connect mechanism.

  • DiscoverServers - Returns all available server instances for a given machine (specified by URL) by calling internally the stack method DiscoveryClient.FindServers.

  • DiscoverServersAsync - asynchronous version of DiscoverServers method.

  • DiscoverServersOnNetwork - Returns all available server instances known to the specified machine, by calling internally the stack method DiscoveryClient.FindServersOnNetwork.

  • DiscoverServersOnNetworkAsync - asynchronous version of DiscoverServersOnNetwork method.

  • GetEndpoints - Gets the available endpoints of the specified server. Depending on the overload that is used this method discovers endpoints directly or by using the Reverse Connect mechanism.

  • GetEndpointsAsync - asynchronous version of GetEndpoints method.

  • GdsGetTrustList- Retrieves the Trust List array from the GDS server, as TrustListDataType per group.

  • GdsRegisterAndSignCertificate - Registers the UaApplication to the GDS server provided in the configuration. It requests a new signed certificate from the GDS server using the applications instance certificate.

ClientSession Class

ClientSession Class

The ClientSession class contains the logic for connecting to an OPC UA Server.

Get new instance of ClientSession

The ClientSession instances are created by the UaApplication Class via the CreateSession, CreateReverseConnectSession or CreateSessionCopy methods:

public ClientSession CreateSession(string url, 
    MessageSecurityMode securityMode = MessageSecurityMode.None, 
    SecurityPolicy securityPolicy = SecurityPolicy.None, 
    MessageEncoding encoding = MessageEncoding.Binary, 
    UserIdentity user = null, 
    string[] locales = null)

This method creates a ClientSession instance that will be able to connect to the specified url, using the specified securityMode, securityPolicy, encoding, user, or locales.

Only the url parameter is mandatory. If the other parameters are not provided the default values are used. If user parameter is null, the session will be initialized with a new UserIdentity that has the AnonymousIdentityToken.

The returned instance will have the SessionType property set on ClientSessionType.Simple.

public ClientSession CreateReverseConnectSession(string reverseConnectUrl, 
    string serverApplicationUri = null,
    MessageSecurityMode securityMode = MessageSecurityMode.None,
    SecurityPolicy securityPolicy = SecurityPolicy.None,
    MessageEncoding encoding = MessageEncoding.Binary,
    UserIdentity user = null,
    string[] locales = null)

This method creates a ClientSession instance that will be able to reverse connect to the specified reverseConnectUrl and serverApplicationUri, using the specified securityMode, securityPolicy, encoding, user, or locales.

Only the reverseConnectUrl parameter is mandatory. If the other parameters are not provided the default values are used.

The returned instance will have the SessionType property set on ClientSessionType.ReverseConnect.

public ClientSession CreateSessionCopy(ClientSession sourceClientSession)

This method creates a ClientSession instance based on another existing instance of ClientSession. The new instance will reuse the custom data type information from the original instance, if available.

The new ClientSession instance is configured to reuse the custom data type information (the ReuseCustomDataTypeInfoAtReconnectflag is set on true even if the existing ClientToolkitConfiguration has it set on false).

In order to have any custom data type information that is copied in the new ClientSession instance, the sourceClientSession must be connected and the custom data type information must be already loaded.

If sourceClientSession parameter is null an ArgumentException will be thrown.

The returned instance will have the SessionType property equal to the SessionType property of the sourceClientSession parameter.

ClientSession Properties - SessionType - gets the type of the session. The possible values are Simple or ReverseConnect. - CoreSession - gets the reference to underlying SDK Core session object. - SessionName - gets the name of the session as it will be known to the server. - Timeout - gets the timeout of the session in milliseconds. The default value is Configuration.ClientConfiguration.DefaultSessionTimeout - RevisedTimeout - gets the value of the timeout revised by the server. - Url - gets or sets the server url for a Simple client session. It can only be set while the session is in Disconnected state. - SecurityMode - gets or sets the security mode (None, Sign, SignAndEncrypt) used for current session. It can only be set while the session is in Disconnected state. - SecurityPolicy - gets or sets the security policy URI used for current session. It can only be set while the session is in Disconnected state. - Encoding - gets or sets the encoding used for current session. It can only be set while the session is in Disconnected state. - UserIdentity - gets or sets the user identity used by the client session. It can only be set while the session is in Disconnected state. - Locales - gets or sets the list of locales used in the provided order by the server to return localized strings. - CheckDomain - gets or sets a flag indicating whether to check the domain of the certificate used to create the session or not. - ApplicationName - gets the application name from the endpoint where current session is connected. - Id - gets the session id as received from the server after the connection was established. - Subscriptions - gets the readonly list of subscriptions created for current session. - Methods - gets the list of methods created for current session. - BrowseDescription - gets or sets the default BrowseDescriptionEx object used for browsing using this session instance. - BrowseHandler - gets the session's BrowseHandler. The BrowseHandler handler helper class provides helper methods for browsing the server's address space. - NamespaceUris - gets the list of primary server's known namespace URIs. - KeepAliveInterval - gets or sets a value (in milliseconds) indicating how frequently the server connection is checked to see whether the communication is still working or not. The default value is set to DefaultKeepAliveInterval. - TypeDictionariesLoaded - gets flag that indicates if the custom data types information was loaded from server dictionaries for current session. - DataTypeDefinitionsLoaded - gets flag that indicates if the custom data type information was loaded from DataTypeDefinition attributes for current session.

Specific to ReverseConnect sessions: - ClientEndpointUrl - gets the client endpoint URL for current ReverseConnect session. - ServerapplicationUri - gets the server application URI for current ReverseConnect session.MaximumWaitForReverseConnectRequest - MaximumWaitForReverseConnectRequest - gets or sets the maximum number of milliseconds that the client session will wait for a server to initiate the reverse connect. Default value is 60000 milliseconds.

ClientSession Methods:

InitializeWithDiscoveryEndpointDescription(EndpointDescription description)

Initializes the Session with the specified endpoint description. This method shall be used to avoid having a GetEndpoints request on connect in the case when the endpoint description is already available.

Update(UserIdentity identity, string[] locales)

Updates the session with the specified identity and locales.

DeleteSubscription(ClientSubscription subscription)

Disconnects and deletes the specified subscription from the current session.

GetDefaultValueForDatatype(NodeId dataType, int valueRank = ValueRanks.Scalar, int arrayLength = 0, int[] arrayDimensions = null)

Gets the default value for the specified data type with the specified value rank, array length or array dimensions.

Read(ReadValueId nodeToRead)

Read the specified attribute of the specified node.

Read(IList<ReadValueId> nodesToRead, double maxAge = 0, TimestampsToReturn timestampsToReturn = TimestampsToReturn.Both)

Read a list of node attributes.

ReadNode(NodeId nodeId)

Read all attributes for the specified node.

Write(WriteValue valueToWrite)

Perform a write operation on a node id and a specified attribute.

Write(List<WriteValue> valuesToWrite)

Perform a write operation for a list of node ids and attributes.

Browse(NodeId nodeId)

Browses the specified node id and returns the list of references. Uses the BrowseDescription set on the session. Uses the session BrowseDescription property.

Browse(NodeId nodeId, BrowseDescriptionEx browseDescription)

Browses the specified node id and returns the list of references. Uses the provided BrowseDescription object.

TranslateBrowsePathToNodeIds(NodeId startingNode, List<QualifiedName> browsePath)

Translates the specified browse path to corresponding NodeIds.

TranslateBrowsePathsToNodeIds(List<BrowsePathEx> browsePaths)

Translates the specified list of browse paths to corresponding NodeIds.

Call(NodeId objectId, NodeId methodId, IList<object> inputArguments, out IList<object> outputArguments)

Calls the specified method and returns the output arguments.

GetMethodArguments(NodeId methodId, out List<Argument> inputArguments, out List<Argument> outputArguments)

Returns a list of input and output arguments for the method with the specified method id.

RegisterNode(NodeId nodeIdToRegister)

Call RegisterNodes service for a node id and get the registered node id.

UnregisterNode(NodeId nodeIdToUnregister)

Call UnregisterNodes service for a node id.

RegisterNodes(IList<NodeId> nodesToRegister)

Call RegisterNodes service for a set of node ids and get the registered node ids

UnregisterNodes(IList<NodeId> nodesToUnregister)

Call UnregisterNodes service for a set of node ids.

Client Configuration

In OPC UA .NET Standard SDK the client configuration is implemented by the ClientToolkitConfiguration class which is loaded as an extension into Application Configuration from OPC UA .NET Standard Stack from OPC Foundation*.*

Any OPC UA client application that uses OPC UA .NET Standard SDK Client Library must start by creating an instance of UaApplicat#ion. UaApplication is an entity that provides configuration, discovery and client session instantiation functionality and it can be created in two ways:

  • with a configuration file path (static configuration)

  • with an instance of Application Configuration (programmatic configuration)

  • with an instance of ApplicationConfigurationBuilderEx by using a fluent configuration API.

Static configuration

The static configuration assumes the existence of a configuration file.

The configuration file approach has the advantage that it can be changed and when the client application is restarted the new values are used without the need to recompile code.

…

// Assume the configuration file is in the same folder with the executable
private const string ConfigurationFilePath = "SampleClient.config.xml";

…

// Create the UaApplication object from config file
UaApplication application = UaApplication.Create(Constants.ConfigurationFilePath).Result;

The returned application object has a Configuration property that contains the parameters loaded from the configuration file and a ClientToolkitConfiguration property which contains OPC UA .NET Standard SDK Client Library specific configuration parameters, as described in ClientToolkitConfiguration class.

To derive own configuration file one could just copy the file SampleClient.config.xml from SampleClient solution to the new project and customize it accordingly.

Note: the order of xml elements from client configuration file is very important. If the order of elements is changed the configuration might be broken after loading from file. Below you have an example that provides the correct order:

<?xml version="1.0" encoding="utf-8"?>

<ApplicationConfiguration
	xmlns:ua="http://opcfoundation.org/UA/2008/02/Types.xsd"
	xmlns="http://opcfoundation.org/UA/SDK/Configuration.xsd"
	schemaLocation="./Schema/ApplicationConfiguration.xsd">
	<ApplicationName>…</ApplicationName>    
	<ApplicationUri>… </ApplicationUri>
	<ProductUri>…</ProductUri>
	<ApplicationType>Client\_1</ApplicationType>
	<SecurityConfiguration>
	<ApplicationCertificate>
		<StoreType>Directory</StoreType>
		<StorePath…</StorePath>
		<SubjectName>…</SubjectName>
	</ApplicationCertificate>
		<TrustedIssuerCertificates>
		<StoreType>Directory</StoreType>
		<StorePath>…</StorePath>
	</TrustedIssuerCertificates>  
	<TrustedPeerCertificates>
		<StoreType>Directory</StoreType>
		<StorePath>…</StorePath>
	</TrustedPeerCertificates>
	<NonceLength>32</NonceLength>
	<RejectedCertificateStore>
		<StoreType>Directory</StoreType>
		<StorePath>…</StorePath>
	</RejectedCertificateStore>
	<AutoAcceptUntrustedCertificates>false</AutoAcceptUntrustedCertificates>
	<RejectSHA1SignedCertificates>false</RejectSHA1SignedCertificates>
	<MinimumCertificateKeySize>1024</MinimumCertificateKeySize>
	</SecurityConfiguration>
	<TransportConfigurations></TransportConfigurations>
	<TransportQuotas>
		<OperationTimeout>120000</OperationTimeout>
		<MaxStringLength>1048576</MaxStringLength>
		<MaxByteStringLength>4194304</MaxByteStringLength>
		<MaxArrayLength>65535</MaxArrayLength>
		<MaxMessageSize>4194304</MaxMessageSize>
		<MaxBufferSize>65535</MaxBufferSize>
		<ChannelLifetime>300000</ChannelLifetime>
		<SecurityTokenLifetime>3600000</SecurityTokenLifetime>
	</TransportQuotas>
	<ClientConfiguration>
		<DefaultSessionTimeout>600000</DefaultSessionTimeout>
		<WellKnownDiscoveryUrls>…</WellKnownDiscoveryUrls>
		<DiscoveryServers></DiscoveryServers>
		<EndpointCacheFilePath>…</EndpointCacheFilePath>
		<MinSubscriptionLifetime>10000</MinSubscriptionLifetime>
	</ClientConfiguration>

	**<Extensions>**  
	**<ua:XmlElement>**
	**<ClientToolkitConfiguration>**

	 
	 <DiscoveryOperationTimeout>10000</DiscoveryOperationTimeout>
	 <DefaultKeepAliveInterval>5000</DefaultKeepAliveInterval>
	 <SessionReconnectDelay>5000</SessionReconnectDelay>      
	 <DefaultSubscriptionPublishingInterval>1000</DefaultSubscriptionPublishingInterval>
	 <DefaultSubscriptionKeepAliveCount>10</DefaultSubscriptionKeepAliveCount>
	 <DefaultSubscriptionLifeTimeCount>1000</DefaultSubscriptionLifeTimeCount>
	 <DefaultSubscriptionMaxNotificationsPerPublish>0</DefaultSubscriptionMaxNotificationsPerPublish>
	 <DefaultSubscriptionPriority>255</DefaultSubscriptionPriority>
	 <DefaultMiSamplingInterval>1000</DefaultMiSamplingInterval>
	 <DefaultMiQueueSize>1</DefaultMiQueueSize>
	 <DefaultEventMiQueueSize>0</DefaultEventMiQueueSize>
	 <DecodeCustomDataTypes>true</DecodeCustomDataTypes>

	**</ClientToolkitConfiguration>**
	**</ua:XmlElement>**
	**</Extensions>**

	<TraceConfiguration>  
		<OutputFilePath…</OutputFilePath>
		<DeleteOnLoad>true</DeleteOnLoad>
		<TraceMasks>519</TraceMasks>
	</TraceConfiguration>

	<DisableHiResClock>true</DisableHiResClock>

</ApplicationConfiguration>

Programmatic configuration

Even if it’s not the recommended way, because is limiting further configuration changes, the client configuration can be defined programmatically in your code like in the code below.

…

// Create new instance of ApplicationConfiguration
ApplicationConfiguration configuration = new ApplicationConfiguration();
configuration.ApplicationName = "UA Sample Client”;

// Create new instance of ClientToolkitConfiguration and set desired properties
ClientToolkitConfiguration clientTkConfiguration = new ClientToolkitConfiguration();
clientTkConfiguration.DiscoveryOperationTimeout = 10000;

// Set ClientToolkitConfiguration instance as an extension of current ApplicationConfiguration object
configuration.UpdateExtension<ClientToolkitConfiguration>(new XmlQualifiedName("ClientToolkitConfiguration"), clientTkConfiguration);

// Set ClientConfiguration
configuration.ClientConfiguration = new ClientConfiguration {DefaultSessionTimeout = 60000};

// Set TraceConfiguration
configuration.TraceConfiguration = new TraceConfiguration()
{
  OutputFilePath = @"%CommonApplicationData%\Softing\OpcUaNetStandardToolkit\logs\SampleClient.log",
  TraceMasks = 519
};

configuration.SecurityConfiguration = new SecurityConfiguration
{
  ApplicationCertificate = new CertificateIdentifier
  {

	  SubjectName = configuration.ApplicationName,
		 StoreType = CertificateStoreType.Directory,
		 StorePath = @"c:\tmp\pki\own"
  },

  TrustedPeerCertificates = new CertificateTrustList
  {
	 StoreType = CertificateStoreType.Directory,
	 StorePath = @"c:\tmp\pki\trusted",
  },

  TrustedIssuerCertificates = new CertificateTrustList
  {
	 StoreType = CertificateStoreType.Directory,
	 StorePath = @"c:\tmp\pki\issuer",
  },

  RejectedCertificateStore = new CertificateTrustList
  {
	 StoreType = CertificateStoreType.Directory,
	 StorePath = @"c:\tmp\pki\rejected",
  },

  AutoAcceptUntrustedCertificates = true
};

// Create new instance of UaApplication from an ApplicationConfiguration object
m\_application = UaApplication.Create(configuration).Result;

…

Programmatic configuration using a fluent API

Besides using XML configuration files to configure instances of OPC UA clients the OPC UA .NET Standard SDK facilitates the creation of such configurations using a fluent configuration API.

The fluent configuration API can be accessed through an instance of the ApplicationConfigurationBuilderEx class, which, through a chained method call sequence, simplifies the parametrization of the configuration*.*

In case there are duplicate method names, one ending with the "Ext" prefix and the other not, the one ending with "Ext" prefix should be used instead!

OPC UA .NET Standard SDK introduces the following "Ext" prefixed extension method:

public static IApplicationConfigurationBuilderSecurityOptions AddSecurityConfigurationExt(
	this IApplicationConfigurationBuilderSecurity appBuilderExtended,
	string subjectName,
	string pkiRoot = null,
	string appRoot = null,
	string rejectedRoot = null)

If the AddSecurityConfigurationExt method is used, than the default values for the folders relative to pkiRoot will be relative to:

"%CommonApplicationData%/Softing/OpcUaNetStandardToolkit/pki"

Additional to the methods added by the stack, OPC UA .NET Standard SDK adds the following methods:

  • public ApplicationConfigurationBuilderEx Initialize(string applicationUri,string productUri)

  • public ApplicationConfigurationBuilderEx SetApplicationName(string applicationName)

  • public ApplicationConfigurationBuilderEx DisableHiResClock(bool value)

and the following extension methods:

  • public static IApplicationConfigurationBuilderSecurityOptions SetUserRoleDirectory(this IApplicationConfigurationBuilderSecurityOptions appBuilderExtended, string userRoleDirectory)

  • public static IApplicationConfigurationBuilderSecurityOptions SetUserRoleDirectory(this IApplicationConfigurationBuilderSecurityOptions appBuilderExtended, string userRoleDirectory)

  • public static IApplicationConfigurationBuilderSecurityOptions SetNonceLength(this IApplicationConfigurationBuilderSecurityOptions appBuilderExtended, int nonceLength)

The names of the methods called to parametrize the configuration are intuitively chosen for each parameter name and need to be called in a specific order as the following example configuration shows.

The creation of a fluent configuration through an instance of the ApplicationConfigurationBuilderEx class starts with specifying the type of desired configuration:

ApplicationConfigurationBuilderEx applicationConfigurationBuilder = new ApplicationConfigurationBuilderEx(ApplicationType.Client);

Subsequently the chained method call sequence through which the parameterization of the configuration follows:

await applicationConfigurationBuilder
	   .Initialize("urn:localhost:Softing:UANETStandardToolkit:SampleClient",                                                                                                         "http://industrial.softing.com/OpcUaNetStandardToolkit/SampleClient")
	   .SetApplicationName("Softing .NET Standard Sample Client")
	   .DisableHiResClock(true)
	   .SetTransportQuotas(new Opc.Ua.TransportQuotas()
	   {
		   OperationTimeout = 120000,
		   MaxStringLength = 1048576,
		   MaxByteStringLength = 4194304,
		   MaxArrayLength = 65535,
		   MaxMessageSize = 4194304,
		   MaxBufferSize = 65535,
		   ChannelLifetime = 300000,
		   SecurityTokenLifetime = 3600000
	   })

The client specific parameterization:

.AsClient()
   .SetDefaultSessionTimeout(610000)
   .SetMinSubscriptionLifetime(11000)
   .AddWellKnownDiscoveryUrls("opc.tcp://{0}:4840/UADiscovery")

Followed by Security specific configuration:

.AddSecurityConfigurationExt(
  "SoftingOpcUaSampleClient",
  "%CommonApplicationData%/Softing/OpcUaNetStandardToolkit/pki",
  "%CommonApplicationData%/Softing/OpcUaNetStandardToolkit/pki",
  "%CommonApplicationData%/Softing/OpcUaNetStandardToolkit/pki")
   .SetRejectSHA1SignedCertificates(false)
   .SetUserRoleDirectory("%CommonApplicationData%/Softing/OpcUaNetStandardToolkit/userRoles")

Then the application domain specific configuration is added using the generic AddExtension<TConfiguration>:

.AddExtension<SampleClientConfiguration>(new XmlQualifiedName("SampleClientConfiguration"),
  new SampleClientConfiguration()
   {
	   ServerUrl = "opc.tcp://localhost:61510/SampleServer",
	   ServerUrlHttps = "https://localhost:61511/SampleServer",
	   ReverseConnectUrl = "opc.tcp://localhost:61512",
	   ReverseConnectServerApplicationUri = "urn:localhost:Softing:UANETStandardToolkit:SampleServer",
	   ReverseConnectServerCertificateIdentifier = new CertificateIdentifier()
	   {
			   StoreType = "Directory",
			   StorePath = "%CommonApplicationData%/Softing/OpcUaNetStandardToolkit/pki/own",
			   SubjectName = "SoftingOpcUaSampleServer"
	   }
   })

.AddExtension<ClientToolkitConfiguration>(new XmlQualifiedName("ClientToolkitConfiguration"),

  new ClientToolkitConfiguration()
   {
	   DiscoveryOperationTimeout = 10000,
	   DecodeCustomDataTypes = true,
	   DecodeDataTypeDictionaries = true
   })

The logging specific parameterization calls:

.SetTraceMasks(1)
.SetOutputFilePath("%CommonApplicationData%/Softing/OpcUaNetStandardToolkit/logs/SampleClient.log")
.SetDeleteOnLoad(true)

Finally the chained methods conclude with the asyncronous Create() method call which concludes the creation of the configuration:

.Create().ConfigureAwait(false);

After the ApplicationConfigurationBuilderEx object is created it can be used to start the OPC UA Client UaApllication.

ApplicationConfiguration

ApplicationConfiguration is the configuration class for OPC UA .NET Standard Stack from OPC Foundation clients.

Together with ClientToolkitConfiguration Class from OPC UA .NET Standard SDK, loaded as an extension, it configures the OPC UA client applications.

TraceConfiguration

The ApplicationConfiguration class, has a TraceConfiguration property described below:

Property Description
OutputFilePath The path of the folder where the file is stored. (Ex. %LocalFolder%\Logs\Opc.Ua.SampleClient.log)
DeleteOnLoad <p>If true at configuration load the previous file having same name is deleted.</p><p>Default value is false.</p>
TraceMask <p>The trace mask is composed of the following bit masks.</p>0x0 – No messages</p><p>0x1 – Error messages</p><p>0x2 – Informational messages</p><p>0x4 – Stack traces</p><p>0x8 – Basic messages for service calls</p><p>0X10 – Detailed messages for service calls</p><p>0X20 – Basic messages for each operation</p><p>0x40 – Detailed messages for each operation</p><p>0x80 – messages related to Initialization and Shutdown</p><p>0x100- messages related to calls of external systems</p><p>0x200 – messages related to security</p><p>0x7FFFFFFF – All messages</p><p> </p><p>Examples:</p><p>1 - Errors only</p><p>513 – Security and Errors</p><p>515 – Security, Errors and Info</p><p>Default value is 0x0.</p>

The object ApplicationConfiguration.TraceConfiguration of type TraceConfiguration (defined in stack) is holding the actual parameters.

They could be changed as in the sample below:

//location of the output file configuration.TraceConfiguration.OutputFilePath = @"%LocalFolder%\Logs\Opc.Ua.SampleClient.log";

//whether the configured output file should be deleted at application configuration load configuration.TraceConfiguration.DeleteOnLoad = true;

//mask inicating what logs are traced configuration.TraceConfiguration.TraceMasks = 519;

The code above is similar with the following xml:

…

<TraceConfiguration>
  <OutputFilePath>%LocalFolder%\Logs\Opc.Ua.SampleClient.log</OutputFilePath>
  <DeleteOnLoad>true</DeleteOnLoad>
  
  
  
  
  
  
  
  
  
  
  
  <TraceMasks>519</TraceMasks>
 </TraceConfiguration>
…

Security configuration

The ApplicationConfiguration has a SecurityConfiguration property of type SecurityConfiguration and contains the security configuration parameters.

Parameter Description
ApplicationCertificate <p>Configuration of the application certificate. object of type CertificateIdentifier.</p><p>For CertificateIdentifier you can configure:</p><p> . StoreType (valid values are "Directory" - local file system</p><p> . StorePath - the path that identifies the certificate store*.*</p><p> . SubjectName - The subject for the certificate</p>
TrustedIssuerCertificates <p>The trusted certificate store. Here you can configure:</p><p> . StoreType (valid values are "Directory" - local file system</p><p> . StorePath - the path that identifies the certificate store*.*</p>
TrustedPeerCertificates <p>The store containing any additional issuer certificates, Here you can configure:</p><p> . StoreType (valid values are "Directory" - local file system</p><p> . StorePath - the path that identifies the certificate store*.*</p>
NonceLength <p>Applications exchange Nonces during the CreateSession. This value specifies the length. Must be >= 32.</p><p>Default value is 32.</p>
RejectedCertificateStore <p>A store where invalid certificates can be placed for later review by the administrator. Here you can configure:</p><p> . StoreType (valid values are "Directory" - local file system</p><p> . StorePath - the path that identifies the certificate store*.*</p>
AutoAcceptUntrustedCertificates <p>Gets or sets a value indicating whether untrusted certificates should be automatically accepted.</p><p>Default value is false. </p><p>The flag is ignored in case the application subscribes to CertificateValidator.CertificateValidation event.</p>
RejectSHA1SignedCertificates <p>Gets or sets a value indicating whether SHA-1 signed certificates are rejected.</p><p>Default value is true.</p>
RejectUnknownRevocationStatus <p>Gets or sets a value indicating whether certificates with unavailable revocation lists are not accepted.</p><p>Default value is false.</p>
MinimumCertificateKeySize <p>Gets or sets a value indicating which minimum certificate key strength is accepted.</p><p>Default value is 2048.</p>
AddAppCertToTrustedStore <p>It is useful for client/server applications running on the same host and sharing the cert store to autotrust.</p><p>Default value is true.</p>
SendCertificateChain <p>Gets or sets a value indicating whether the application should send the complete certificate chain.</p><p>Default value is false.</p>
SuppressNonceValidationErrors <p>Gets or sets a value indicating whether the server nonce validation errors should be suppressed.</p><p>If set to true the server nonce validation errors are suppressed.</p><p>Default value is false.</p>

Other information related to security configuration:

CertificateValidator.CertificateValidation event <p>By handling this event the application can override the default certification validation.</p><p>Handle the CertificateValidation Errors explains how to handle this event.</p>
UaApplication.Configuration.Validate() method <p>By calling this method the SDK loads and validates the UaApplication.Configuration.SecurityConfiguration property.</p><p> </p><p>Calling this method is mandatory, otherwise the SecurityConfiguration will not be properly initialized.</p>

Handle the CertificateValidation Errors

The ApplicationConfiguration.CertificateValidator object provides an event called CertificateValidation that shall be handled to receive notification and react at the certificate validation errors encountered by current CertificateValidator.

The CertificateValidationEventArgs has properties:

  • Error has type ServiceResult and represents the current ServiceResult that is not Good. It has an InnerResult property of ServiceResult type* that will hold the chained inner error, if any.

  • Certificate has type X509Certificate2 and holds reference to the certificate that is validated.

  • Accept has bool type. If set on true the current Error code is accepted but the InnerResult of this object, if not null, will raise again the CertificateValidation event.

  • AcceptAll has bool type. If set on true the entire chain of Error is accepted and the Certificate will be accepted.

The Program.cs file from SampleClient contains a sample of CertificateValidation event handling where AcceptAll flag is set on true and the entire Error chain is iterated and printed to console.

/// <summary>
/// Event handler received when a certificate validation error occurs.
/// </summary>
/// <param name="validator"></param>
/// <param name="e"></param>
public static void CertificateValidator_CertificateValidation(CertificateValidator validator, CertificateValidationEventArgs e)
{          

	// display certificate info
	Console.WriteLine("Certificate could not be validated!");
	Console.WriteLine("Subject: {0}", e.Certificate.Subject);
	Console.WriteLine("Issuer: {0}", ((e.Certificate.Subject == e.Certificate.Issuer) ? "Self-signed" : e.Certificate.Issuer));
	Console.WriteLine("Valid From: {0}", e.Certificate.NotBefore);
	Console.WriteLine("Valid To: {0}", e.Certificate.NotAfter);
	Console.WriteLine("Thumbprint: {0}", e.Certificate.Thumbprint);
	Console.WriteLine("Validation error(s): ");

	// intialize the value for e.AcceptAll with true. This means that all status codes are accepted.
	bool isCertificateAccepted = true;  

	// Implement the logic that decides if the certificate can be accepted or not and set isCertificateAccepted flag accordingly.
	// The certificate can be retrieved from the e.Certificate field.
	ServiceResult error = e.Error;          

	while (error != null)
	{
	 Console.WriteLine(error);

	 // decide if error is acceptable and certificate can be trusted 
	 // move to InnerResult
	 error = error.InnerResult;
	}

	if (isCertificateAccepted)
	{
	 Console.WriteLine(" The Certificate was accepted! ");
	}
	else
	{
	 Console.WriteLine(" The Certificate was NOT accepted! ");
	}

	// Set the AcceptAll flag to signal the CertificateValidator if the Certificate shall be accepted.
	// If this flag is set on true the CertificateValidation events for e.Error.InnerResult will be supressed.

	e.AcceptAll = isCertificateAccepted;

}

ClientToolkitConfiguration

ClientToolkitConfiguration is the configuration class for OPC UA .NET Standard SDK clients.

It is intended to be loaded as an extension of ApplicationConfiguration and has the following parameters:

|Parameter|Description| | :- | :- | |DiscoveryOperationTimeout|<p>Gets or sets the discovery operation time-out.</p><p>Default value is 5000.</p>| |DefaultKeepAliveInterval|<p>This represents how frequently the server is pinged to see if communication is still working.</p><p>Cannot have less than 1000 ms.</p><p>Default value is 5000.</p>| |SessionReconnectDelay|<p>The period of time at which the Session will do a reconnect attempt if disconnected.</p><p>Default value is 5000.</p>| |DefaultSessionTimeout|<p>Time that a session shall remain open without activity.</p><p>This property is deprecated.</p><p>Use** ApplicationConfiguration.ClientConfiguration.DefaultSessionTimeout instead. </p><p>Default value is 0.</p>| |DefaultSubscriptionPublishingInterval|<p>The period at which the Subscription will send Notification messages.</p><p>Default value is 1000.</p>| |DefaultSubscriptionKeepAliveCount|<p>Counts the number of consecutive publishing cycles in which there has been no notification to report to the Client. </p><p>When the maximum keep-alive count is reached, a Publish request is de-queued and used to return a keep-alive Message.</p><p>Default value is 10.</p>| |DefaultSubscriptionLifeTimeCount|<p>This parameter sets how many times the publishing interval can expire before the subscription is terminated.</p><p>Default value is 1000.</p>| |DefaultSubscriptionMaxNotificationsPerPublish|<p>A value of zero indicates that there is no limit. The number of notifications per Publish is the sum of monitored items in the DataChangeNotification and events in the EventNotificationList.</p><p>Default value is 0.</p>| |DefaultSubscriptionPriority|<p>When more than one Subscription needs to send Notifications, the Server should de-queue a Publish request to the Subscription with the highest priority number. For Subscriptions with equal priority the Server should de-queue Publish requests in a round-robin fashion. A Client that does not require special priority settings should set this value to zero.</p><p>Default value is 255.</p>| |DefaultMiSamplingInterval|<p>Each MonitoredItem created by the Client is assigned a sampling interval that is either inherited from publishing interval of the Subscription or that is defined specifically to override that rate. A number indicates that the default sampling interval defined by the publishing interval of the Subscription is requested. The sampling interval indicates the fastest rate at which the Server should sample its underlying source for data changes.</p><p>Default value is 1000.</p>| |DefaultMiQueueSize|<p>Gets or sets the default queue size of the monitored item.</p><p>Default value is 1.</p>| |DefaultEventMiQueueSize|<p>Gets or sets the default queue size of the event monitored item.</p><p>Default value is 0.</p>| |DecodeCustomDataTypes|<p>Gets or sets a value indicating whether the client application should load the custom data type information from DataTypeDefinition attribute of DataType nodes from server at connect.</p><p>Default value is false.</p>| |DecodeDataTypeDictionaries|<p>Gets or sets a value indicating whether the client application should load the custom data type dictionaries from server at connect.</p><p>Default value is false.</p>| |ClientCertificateLifeTime|<p>Gets or sets the lifetime of the Client Certificate in months.</p><p>Default value is 12.</p>| |ReuseCustomDataTypeInfoAtReconnect|<p>Gets or sets a flag that indicates if the custom data type information loaded from the server when the DecodeCustomDataTypes and/or the DecodeDataTypeDictionaries configuration flags are true is reused when the ClientSession reconnects after a failed keep alive event.</p><p>This flag is used only if any of the other two flags is true:</p><p>·When this flag is false the custom data type information is loaded at each session reconnect. </p><p>When this flag is true the custom data type information is loaded only once, when the client session connects. At each reconnect the custom data type information is reused. </p> <p>Default value is false.</p>| The ClientToolkitConfiguration is available as property ClientToolkitConfiguration of the UaApplication Class.

This is how the ClientToolkitConfiguration must be specified in xml configuration file for a client:

<?xml version="1.0" encoding="utf-8"?>
<ApplicationConfiguration
	xmlns:ua="http://opcfoundation.org/UA/2008/02/Types.xsd"
	xmlns="http://opcfoundation.org/UA/SDK/Configuration.xsd"
	schemaLocation="./Schema/ApplicationConfiguration.xsd">
  <SecurityConfiguration>…</SecurityConfiguration>
  <ClientConfiguration>… </ClientConfiguration>
  <Extensions>  
   <ua:XmlElement>
	 <ClientToolkitConfiguration>
	 
	 <DiscoveryOperationTimeout>10000</DiscoveryOperationTimeout>
	 <DefaultKeepAliveInterval>5000</DefaultKeepAliveInterval>
	 <SessionReconnectDelay>5000</SessionReconnectDelay>
	 <DefaultSessionTimeout>60000</DefaultSessionTimeout>
	 <DefaultSubscriptionPublishingInterval>1000</DefaultSubscriptionPublishingInterval>
	 <DefaultSubscriptionKeepAliveCount>10</DefaultSubscriptionKeepAliveCount>
	 <DefaultSubscriptionLifeTimeCount>1000</DefaultSubscriptionLifeTimeCount>
	 <DefaultSubscriptionMaxNotificationsPerPublish>0</DefaultSubscriptionMaxNotificationsPerPublish>
	 <DefaultSubscriptionPriority>255</DefaultSubscriptionPriority>
	 <DefaultMiSamplingInterval>1000</DefaultMiSamplingInterval>
	 <DefaultMiQueueSize>1</DefaultMiQueueSize>
	 <DefaultEventMiQueueSize>0</DefaultEventMiQueueSize>
	 <DecodeCustomDataTypes>true</DecodeCustomDataTypes>
	 <DecodeDataTypeDictionaries>true</DecodeDataTypeDictionaries>
	 </ClientToolkitConfiguration>
   </ua:XmlElement>
  </Extensions>
  <TraceConfiguration>…</TraceConfiguration>
</ApplicationConfiguration>

Note: Please be aware that the order of ClientToolkitConfiguration properties is important, it must not be changed.

GdsConnectionConfiguration

GdsConnectionConfiguration is the configuration class for a connection to a GDS from OPC UA .NET Standard SDK clients.

It is intended to be loaded as an extension of ApplicationConfiguration and has the following parameters:

Parameter Description
GdsUrl <p>Gets or sets the GDS endpoint url.</p><p>Default value is null.</p>
MessageSecurityMode <p>Gets or sets the Opc.Ua.MessageSecurityMode that will be used to connect to GDS.</p><p>Default value is MessageSecurityMode.Invalid.</p>
SecurityPolicy <p>Gets or sets the Opc.Ua.SecurityPolicy that will be used to connect to GDS.</p><p>Default value is SecurityPolicy.None.</p>
MessageEncoding <p>Gets or sets the Opc.Ua.MessageEncoding that will be used to connect to GDS.</p><p>Default value is MessageEncoding.Binary.</p>
PreferredLocales <p>Gets or sets the preferred locals used when connecting to the GDS endpoint url.</p><p>Default value is null.</p>

This is how the GdsConnectionConfiguration must be specified in xml configuration file for a client:

<?xml version="1.0" encoding="utf-8"?>
<ApplicationConfiguration
	xmlns:ua="http://opcfoundation.org/UA/2008/02/Types.xsd"
	xmlns="http://opcfoundation.org/UA/SDK/Configuration.xsd"
	schemaLocation="./Schema/ApplicationConfiguration.xsd">
  <SecurityConfiguration>…</SecurityConfiguration>
  <ClientConfiguration>… </ClientConfiguration>
  <Extensions>  
   <ua:XmlElement>
	<GdsConnectionConfiguration>
	 
	 <GdsUrl>opc.tcp://localhost:58810/GlobalDiscoveryTestServer</GdsUrl>
	 
	 <MessageSecurityMode>SignAndEncrypt\_3</MessageSecurityMode>
	 <SecurityPolicy>Basic256Sha256</SecurityPolicy>
	 <MessageEncoding>Binary</MessageEncoding>
	 <PreferredLocales>
	  <Locale>en-us</Locale>
	  <Locale>de</Locale>
	 </PreferredLocales>
	</GdsConnectionConfiguration>
  </ua:XmlElement>
  </Extensions>
  <TraceConfiguration>…</TraceConfiguration>
</ApplicationConfiguration>

Use the following code to obtain the object that contains the configured values for the UaApplication instance:

GdsConnectionConfiguration gdsConnectionConfiguration = m_application.Configuration.ParseExtension<GdsConnectionConfiguration>();

Note: Please be aware that the order of GdsConnectionConfiguration properties is important, it must not be changed.

Discovery

The Discovery mechanism is implemented in UaApplication class via three methods: DiscoverServers, DiscoverServersOnNetwork and GetEndpoints.

The UaApplication class also implemented asynchronously three methods for Discovery: DiscoverServersAsync, DiscoverServersOnNetworkAsync and GetEndpointsAsync.

The method UaApplication.DiscoverServers method will return all available server instances for a given machine (specified by URL) by calling internally the stack method DiscoveryClient.FindServers.

The method UaApplication.DiscoverServersOnNetwork method will return all available server instances known to the specified machine, by calling internally the stack method DiscoveryClient.FindServersOnNetwork.

The method UaApplication.DiscoverServersAsync method will return asynchronously all available server instances for a given machine (specified by URL) by calling internally the stack method DiscoveryClient.FindServers.

The method UaApplication.DiscoverServersOnNetworkAsync method will return asynchronously all available server instances known to the specified machine, by calling internally the stack method DiscoveryClient.FindServersOnNetwork.

Then for a running server it is needed to retrieve its endpoints as objects of type EndpointDescriptionEx (UaApplication.GetEndpoints or asynchronously UaApplication.GetEndpointsAsync service calls*).*

It is recommended to enclose any service call inside a try-catch block and to log a relevant message on exception catch.

try
{
  var servers = uaApplication.DiscoverServers("opc.tcp://localhost:4840");
}
catch (Exception e)
{
  Utils.Trace(e,"DiscoverServers Error");
}

or asynchronously

try
{
  var servers = await uaApplication.DiscoverServersAsync("opc.tcp://localhost:4840");
}
catch (Exception e)
{
  Utils.Trace(e,"DiscoverServersAsync Error");
}

The GetEndpoints receives a server URL as parameter and returns a list of EndpontDescriptionEx objects.

Below it’s a usage example of GetEndpoints method. The try-catch structure is recommended for error handling.

try
{
  var endpoints = uaApplication.GetEndpoints("opc.tcp://localhost:51510/UA/DemoServer");
}
catch (Exception e)
{
  Utils.Trace(e,"GetEndpoints Error");
}

or asynchronously

try
{
  var endpoints = await uaApplication.GetEndpoints("opc.tcp://localhost:51510/UA/DemoServer");
}
catch (Exception e)
{
  Utils.Trace(e,"GetEndpointsAsync Error");
}

Connect

The Connect mechanism is implemented in ClientSession class.

Before establishing the connection a session object (of type ClientSession) is required to be instantiated (by a UaApplication.CreateSession method call).

Note: The ClientSession instance returned by UaApplication.CreateSession method will have SessionType property set to ClientSessionType.Simple.

A client-server connection is finally obtained by calling the Connect method on the session object.

// create the session object with no security and anonymous login  
m_session = m_application.CreateSession(Constants.ServerUrl);
session.SessionName = SessionName;

try
{
  //connect session
  session.Connect(false, true);
}
catch (Exception e)
{
  Utils.Trace(e,"Connect Session Error");
}

or using asynchronously call

// create the session object with no security and anonymous login  
m_session = m_application.CreateSession(Constants.ServerUrl);
session.SessionName = SessionName;

try
{
  //connect session
  await session.ConnectAsync(false, true);
}
catch (Exception e)
{
  Utils.Trace(e,"Connect Session Error");
}

After concluding the service calls, the ClientSession.Disconnect method is used to close the connection and release the resources allocated for it both on client and server.

Reverse Connect

The Reverse Connect mechanism is implemented in ClientSession class.

Before establishing the reverse connection a reverse connect session object (of type ClientSession) is required to be instantiated (by a UaApplication.CreateReverseConnectSession method call).

/// <summary>
///  Creates and initializes a new instance of the ClientSession class that has property SessionType = ClientSessionType.ReverseConnect.
/// </summary>
/// <param name="reverseConnectUrl">the url of the endpoint where the server publishes the ReverseHello messages.</param>
/// <param name="serverApplicationUri">optional. The server application URI that needs to be matched for the incoming ReverseHello messages.</param>
/// <param name="securityMode">MessageSecurityMode for new ClientSession object.</param>
/// <param name="securityPolicy">SecurityPolicy for new ClientSession object.</param>
/// <param name="encoding">MessageEncoding for new ClientSession object.</param>
/// <param name="user">UserIdentity for new ClientSession object.</param>
/// <param name="locales">Array of locales for new ClientSession object.</param>
/// <returns></returns>

public ClientSession CreateReverseConnectSession(string reverseConnectUrl, string serverApplicationUri = null, MessageSecurityMode securityMode = MessageSecurityMode.None, SecurityPolicy securityPolicy = SecurityPolicy.None, MessageEncoding encoding = MessageEncoding.Binary, UserIdentity user = null, string[] locales = null)

Note: The reverseConnectUrl parameter shall point to the local machine. Even if the URL points to another machine the reverse connect listener is created on local machine on the specified port.

A client-server connection is finally obtained by calling the Connect methods on the reverse session object.

// create the reverse connect session object with no security and anonymous login  
ClientSession session = m_application.*CreateReverseConnectSession*(***m\_reverseConnectUrl***);

session.Timeout = 100000;
session.MaximumWaitForReverseConnectRequest = 100000;
session.SessionName = SessionName;

try
{
  //connect session
  session.Connect(false, true);
}
catch (Exception e)
{
  Utils.Trace(e,"Reverse Connect Session Error");
}

After concluding the service calls, the ClientSession.Disconnect method is used to close the reverse connection and release the resources allocated for it both on client and server.

Browse

The Browse mechanism is implemented in ClientSession class by the Browse method. The method takes parameter a Node Id and the browse operation will return the list of references from that Node Id taking into account the browse options.

The Browse method is overloaded and has two signatures. One method takes the default browse options set on the session and the other requires the browse options as an input parameter. The latter one can be used to do multiple browse on the same session with different browse options.

Configurable Browse

public virtual IList<ReferenceDescriptionEx> Browse(NodeId nodeId, BrowseDescriptionEx browseOptions)

The return of the browse operation can be configured via the BrowseOptions class. By setting the different properties of the class the user can modify:

  • The browse direction (see possible values of Opc.Ua.BrowseDirection)

  • The number of references to return (MaxReferencesReturn, ContinueUntilDone)

  • The type of references to return(ReferenceTypeId)

  • The type of nodes to return (TypeId, IncludeSubtypes)

  • The node class (NodeClassMask) – example Opc.Ua.NodeClass.VariableType | Opc.Ua.NodeClass.ObjectType

  • The ResultMask (see possible values of Opc.Ua.BrowseResultMask)

The number of references returned by the method call is not necessarily the complete list of references found from that node. For example, by setting the Browse options property MaxReferencesReturned to 5 if the node has more than 5 references and ContinueUntilDone flag is set on false, only the first 5 references are returned. In the same situation if ContinueUntilDone flag is set on true, MaxReferencesReturned value is ignored and all references are returned, the only difference from having MaxReferencesReturned set is that the references are received in batches from the server, batch size is MaxReferencesReturned.

Usage example for Browse with browse options specified:

BrowseDescriptionEx options = new BrowseDescriptionEx();

options.MaxReferencesReturned = 5;

IList<ReferenceDescriptionEx> objectReferenceDescriptions = m_session.Browse(nodeId, options);

Read

Reading nodes information from the address space is most common operation performed by an UA client application.

The class Softing.Opc.Ua.Client.ClientSession has 3 methods for synchronous reading of node attributes.

public virtual BaseNode ReadNode(NodeId nodeId)

ReadNode method returns an instance of a class derived from BaseNode depending on the node class (either: VariableNodeEx, VariableTypeNodeEx, ViewNodeEx, ReferenceTypeNodeEx, ObjectTypeNodeEx, ObjectNodeEx, MethodNodeEx, DataTypeNodeEx).

Starting with version 2.50 if the node cannot be found or the NodeClass attribute cannot be read then an instance of VariableNodeEx will be returned. Use GetStatusCode() method from BaseNode to get the status code returned by the server for each AttributeId.

Throws an exception if the argument is null, the session is disconnected or errors occurred during client-server data exchange.

public virtual DataValueEx Read(ReadValueId nodeToRead)

This implementation of Read method returns null if the node does not exist or a DataValue instance if the node exists and the node access level has CurrentRead granted.

Throws an exception if the argument is null, the session is disconnected or errors occurred during client-server data exchange.

public virtual IList<DataValueEx> Read(IList<ReadValueId> nodesToRead, double maxAge, TimestampsToReturn timestampsToReturn)

This implementation of Read method performs a multiple read operation. Returns a list of DataValues. The method has 3 parameters:

nodesToRead: list of ReadValueId specifying the node id and the attribute to read.

maxAge: value expressed in milliseconds representing the maximum difference between the ServerTimestamp and the time when the Server starts processing the read request.

timestampsToReturn: enum specifying which timestamps to return (source, server or both).

Source’s timestamp is the timestamp of the data change occurred in the source system(ex. the PLC)

Server’s timestamp is the timestamp of the data change occurred in the UA server.

For the Read method overloads which are returning DataValuesEx it is important to check the value of the StatusCode enum property. Depending on the use case, the value is considered a valid if equal to either StatusCode.IsGood, StatusCode.IsNotBad.

It is a good practice to enclose the Read method calls within a try-catch block and to log the exception. (see Logging).

DataValueEx is a wrapper class over stack DataValue class and it provides support for complex type values via it's new property ProcessedValue. This new property holds the value of wrapped DataValue object transformed in types that are not accepted by the stack implementation (e.g. complex type values, custom enumerations, Matrix of complex data types)

Write

Having established an active client session, performing synchronous write calls can be done by using any of the 2 Write methods of the class Softing.Opc.Ua.Client.ClientSession.

public virtual StatusCode Write(WriteValue valueToWrite)

This implementation of Write performs a single write operation.

The method argument is an object (of type WriteValue) which encapsulates the node id and the attribute to change the value for.

public virtual IList<StatusCode> Write(List<WriteValue> valuesToWrite)

This implementation of Write performs a multiple write operation.

The method argument is a list of WriteValue objects which are indicating the node respectively the attribute to update.

The method calls are returning the status codes for each individual updated attribute.

It is recommended to check always the returned status code, to enclose the method call inside a try-catch block and to adapt accordingly the further processing of your client application in case of failures or thrown exceptions.

Register Nodes

Starting with version 2.70 the OPC UA .NET Standard SDK Client Library provides methods for calling the RegisterNodes and UnregisterNodes service.

The class Softing.Opc.Ua.Client.ClientSession has 4 methods for registering and unregistering nodes in the context of a session.

public virtual NodeId RegisterNode(NodeId nodeIdToRegister)

RegisterNode method calls the RegisterNodes Service on the server for one NodeId and returns the newly assigned NodeId.

The RegisterNodes Service can be used by Clients to register the Nodes that they know they will access repeatedly (e.g. Write, Call). It allows Servers to set up anything needed so that the access operations will be more efficient. Clients can expect performance improvements when using registered NodeIds, but the optimization measures are vendor-specific.

The client application shall use the received NodeId for subsequent Read/Write/Call calls from the same session.

public virtual IList<NodeId> RegisterNodes(IList<NodeId> nodesToRegister)

RegisterNodes method calls the RegisterNodes service on the server for a list of NodeIds and returns the newly assigned NodeIds collection.

The client application shall use the received NodeIds collection for subsequent Read/Write/Call calls from the same session.

Note: The nodes shall be unregistered as soon as possible in order to free server resources.

public virtual void UnregisterNode(NodeId nodeIdToUnregister)

UnregisterNode method calls the UnregisterNodes Service on the server for one NodeId. This Service is used to unregister NodeIds that have been obtained via the RegisterNodes service.

public virtual void UnregisterNodes(IList<NodeId> nodesToUnregister)

UnregisterNodes method calls the UnregisterNodes Service on the server for a list of NodeIds. This Service is used to unregister NodeIds that have been obtained via the RegisterNodes service.

Sample code is available in Register Nodes section.

Monitored Item

The item monitoring mechanism is implemented in the ClientMonitoredItem class. The class represents an object used for monitoring value changes or events. The object is used for cyclic update of a server located node or for receiving server events. The subscription (implemented in the ClientSubscription class) holds a list of monitored items. Depending on what needs to be monitored (datachange or an event) two class constructor overloads could be used.

Subscription

The subscription is used as a container of the monitored items collection. It creates a cyclic update of a server's node attributes or events through monitored items. The subscription object has methods to connect and disconnect both itself and the contained monitored items.

Also, it maintains internally state information like the CurrentState and TargetState.

Example of creating a subscription, a session is instantiated first and it is used as parameter for the subscription:

// create the session object with no security and anonymous login  
m_session = m_application.CreateSession(Constants.ServerUrl);
m_session.SessionName = SessionName;
m_session.Connect(false, true);
m_subscription = new ClientSubscription(m_session, SubscriptionName);

// set the Publishing interval for this subscription
m_subscription.PublishingInterval = 500;

A session needs to be instantiated before the subscription and a subscription is required to be instantiated before a monitored item can be created. Once a monitored item is created it is automatically added to its parent subscription.

When a new subscription is created, the value of the UaApplication.ClientToolkitConfiguration.DefaultSubscriptionPublishingInterval parameter is used for setting the PublishingInterval property.

Note: If parent ClientSession is in Connected State the new ClientSubscription will be automatically connected.

Revised Properties section describes the properties that can be revised by the server.

Monitored item for data changes

The constructor has signature:

public ClientMonitoredItem(ClientSubscription subscription, NodeId nodeId, string displayName, uint attributeId = Attributes.Value, string indexRange = null)

Inside the constructor:

  • If the subscription is connected or active, the monitored item will be connected automatically.

  • attributeId and indexRange parameters are optional.

  • The value of the UaApplication.ClientToolkitConfiguration.DefaultMiSamplingInterval parameter is used for setting the SamplingInterval property.

  • The value of UaApplication.ClientToolkitConfiguration.DefaultMiQueueSize parameter is used for setting the QueueSize property.

  • We can control which attribute of the node is to be monitored (argument attributeId, for this parameter use constants defined in stack class Opc.Ua.Attributes). When no attributeId is specified the default value used is Attributes.Value.

Note: Revised Properties section describes the properties that can be revised by the server.

For each data change received from the server the DataChangesReceived event is raised. Below is a code fragment with an example of handling this event.

…

ClientMonitoredItem monitoredItem = new ClientMonitoredItem(m_subscription, nodeId, "Sample Monitored Item");

// attach event handler for DataChangesReceived event
monitoredItem.DataChangesReceived += Monitoreditem_DataChangesReceived;

…

private void Monitoreditem\_DataChangesReceived(object sender, DataChangesNotificationEventArgs e)
{
  foreach (var dataChangeNotification in e.DataChangeNotifications)
  {
	 Console.WriteLine(" {0} Received data value change for monitored item:", dataChangeNotification.SequenceNo);
	 Console.WriteLine("    Value : {0} ", dataChangeNotification.Value);
	 Console.WriteLine("    StatusCode : {0} ", dataChangeNotification.Value.StatusCode);
	 Console.WriteLine("    ServerTimestamp : {0:hh:mm:ss.fff tt}", dataChangeNotification.Value.ServerTimestamp.ToLocalTime());
	 Console.WriteLine("    SourceTimestamp : {0:hh:mm:ss.fff tt}", dataChangeNotification.Value.SourceTimestamp.ToLocalTime());
  }
}

Note: It is possible to receive notifications for a MonitoredItem if it’s Timestamp property to changed without changes in Value property. For this purpose it is necessary to set the MonitoredItem.Filter property to a DataChangeFilter instance with Trigger property set to DataChangeTrigger.StatusValueTimestamp.

monitoredItem.Filter.Trigger = DataChangeTrigger.StatusValueTimestamp;

Monitored items for events

Constructor for monitoring events on nodes:

public ClientMonitoredItem(ClientSubscription subscription, NodeId nodeId, string displayName, EventFilterEx eventFilter)

In the constructor:

  • If the subscription is connected or active, the monitored item will be connected automatically

  • The value of the UaApplication.ClientToolkitConfiguration.DefaultMiSamplingInterval parameter is used for setting the SamplingInterval property

  • The value of UaApplication.ClientToolkitConfiguration.DefaultEventMiQueueSize parameter is used for setting the QueueSize property

  • If the EventFilter is not specified, the default event filter will be used. This default filter does not have a where clause specified, only the select clause for the following properties of the BaseEventType: EventId, EventType, SourceNode, SourceName, Time, ReceiveTime, LocalTime, Message, Severity.

If not null the filter has a select clause and a where clause:

  • Where Clause - can specify the type of the events to be received from the server, by setting the EventTypeIdFilter property of the filter.

  • Select Clause - can specify the fields of the events for which their values should be sent from the server. For this the AddSelectClause method can be used.

Below is a code fragment with an example of handling the event filter.

m_eventMonitoredItem = new ClientMonitoredItem(m_subscription, ObjectIds.Server, "Sample Event Monitored Item", null);

// attach event handler for EventsReceived event
m_eventMonitoredItem.EventsReceived += m_eventMonitoredItem_EventsReceived;

…

private void m_eventMonitoredItem\_EventsReceived(object sender, EventsNotificationEventArgs e)
{
  foreach (var eventNotification in e.EventNotifications)
  {
	 Console.WriteLine("Event notification received for {0}.\n", eventNotification.MonitoredItem.DisplayName);

	 StringBuilder displayNotification = new StringBuilder();
	 IList<SelectOperandEx> listOfOperands = ((EventFilterEx)m_eventMonitoredItem.Filter).SelectOperandList;

	 for (int i = 0; i < listOfOperands.Count; i++)
	 {
	    displayNotification.AppendFormat("{0}:{1}:{2}\n",
			   listOfOperands[i].PropertyName.NamespaceIndex ,
			   listOfOperands[i].PropertyName.Name,
			   eventNotification.EventFields[i]);
	 }

	 Console.WriteLine(displayNotification);
  }
}

Note: It is possible to change the Filter property after the MonitoredItem is created.:

EventFilterEx filter = (EventFilterEx)m_eventMonitoredItem.Filter;
filter.EventTypeIdFilter = *<EventNodeId>*;
filter.AddSelectClause(*<EventNodeId>*, *<PropertyBrowseName>*);

// Set back the resulting filter object to monitored item Filter property
m_eventMonitoredItem.Filter = filter;

Read DataType

The ReadDataType method of the monitored item, reads the data type attribute of the node that is monitored and sets its DataType property.

By default the DataType property is null, only the ReadDataType method call should set it. Reading and setting of the data type is not done by default because of performance reasons.

The data type is used to try to convert the returned value to an enum value if it is the case (if the monitored node holds an enumeration value). Both the Notification event and the Read method will return enum values if the node's value type is enum and the DataType property is loaded.

The static ReadDataType is a helper method that reads the data type for the list of monitored items provided as input arguments.

Revised Properties

When a ClientSubscription or a ClientMonitoredItem is connected to the server, the server can revise some of the properties used to configure these objects.

ClientSubscription has the following revised properties:

  • RevisedLifeTimeCount - The Server calculates the LifeTimeCount so that the subscription lifetime is no longer than the ServerConfiguration.MaxSubscriptionLifetime and* the lifetime is greater than the keep alive interval

  • RevisedMaxKeepAliveCount - The Server calculates the KeepAliveCount so that the keep alive interval is not longer than the max subscription lifetime and the time between publishes does not exceed the max publishing interval

  • RevisedPublishingInterval - Usually it is equal to PublishingInterval but the value shall not be smaller than ServerConfiguration.MinPublishingInterval or ServerConfiguration.PublishingResolution. It shall not be greater than ServerConfiguration.MaxPublishingInterval.

The Configuration properties are described in Server Configuration section.

ClientMonitoredItem has the following revised properties:

  • RevisedQueueSize - is the value of QueueSize property returned by the server after creating the monitored item.

  • RevisedSamplingInterval- is the value of SamplingInterval property returned by the server after creating the monitored item.

Complex Data Types

Complex data types are implemented in Softing.Opc.Ua.Client.Types namespace.

Note: A precondition for reading complex data types in a ClientSession is to load DataTypeDefintion attribute of data type nodes or to read and parse data type dictionaries for that session instance.

  • The DataTypeDefintion attribute from server's data type nodes is automatically parsed if DecodeCustomDataTypes flag from ClientToolkitConfiguration is set on true. (see ClientToolkitConfiguration). The ClientSession created with DecodeCustomDataTypes = true will call its FetchDataTypeDefinitions() method asynchronously and the DataTypeDefinitionsLoaded flag will be set on true when the operation ends.

  • The data type dictionaries are automatically read and parsed from server's address space if DecodeDataTypeDictionaries flag from ClientToolkitConfiguration is set on true. (see ClientToolkitConfiguration). The ClientSession created with DecodeDataTypeDictionaries = true will call its FetchDataDictionaries() method asynchronously and the TypeDictionariesLoaded flag will be set on true when the operation ends.

      //wait until DataTypeDefinition attributes are loaded
      if (m_application.ClientToolkitConfiguration.DecodeCustomDataTypes)
      {
    
        while (!m_session.DataTypeDefinitionsLoaded)
        {
      	 Task.Delay(500).Wait();
        }
      }
    
      //wait until type dictionaries are loaded
      if (m_application.ClientToolkitConfiguration.DecodeDataTypeDictionaries)
      {
        while (!m_session.TypeDictionariesLoaded)
        {
      	 Task.Delay(500).Wait();
        }
      }
    

After loading the data type information in the ClientSession it is possible to use the helper classes defined in Softing.Opc.Ua.Client.Types namespace.

When reading a node value that has a complex data type value the ClientSession.Read will return a DataValueEx object with ProcessedValue property set to an instance or collections of BaseComplexTypeValue type. Depending on the actual type definition of the complex data type value that is read the instance will have its specific runtime type and it can be determined using the image from above.

A StructuredValue object has a property Fields, which is a list of StructuredField type. Each StructuredField has a Name, Value, Type and EncodingType property. StructuredField instances can have Value of StructuredValue type.

DataType5Variable is an instance of DataType5 complex type. It has 5 fields:

  • Int32Field - of Int32 type

  • FloatField - of Float type

  • StringField - of String type

  • DataType2Field - of DataType2 complex type

  • EnumerationType1Field - of EnumerationType1 enumeration type

  • The OptionalFieldsStructuredValue is derived from StructuredValue and has an additional property: EcodingMask. It can be used to specify which optional fields from the OptionalFieldsStructuredValue are used and its value is a bit mask for all optional fields declared in data type definition. For example if there is an instance of OptionalFieldsStructuredValue that has 3 optional fields each field has a bit assigned to it and if the list:

    • Field 1 - mandatory
    • Field 2 - optional - has bit 1
    • Field 3 - mandatory
    • Field 4 - optional - has bit 2
    • Field 5 - optional - has bit 3

If it is needed to set and store Field 2 and Field 4 then the EcodingMask = 1 + 23 = 9.

  • The UnionStructuredValue is derived from StructuredValue and has an additional property: SwitchFieldPosition that represents the 1 based index of the field that will be encoded/decoded for that particular instance of UnionStructuredValue. For example if there is an instance of UnionStructuredValue that has 4 fields:

    • Field 1 - position 1
    • Field 2 - position 2
    • Field 3 - position 3
    • Field 4 - position 4

If it is needed to set and store Field 2 then the SwitchFieldPosition = 2.

Note: StructuredValue fields can have ValueRank property defined as Scalar, OneDimension or TwoDimensions. SampleServer defines a data type that has fields with all these value ranks. Search for "StructureWithValueRanksType" in CustomTypesNodeManager.cs file for sample code.

  • The OptionSetValue is derived from BaseComplexTypeValue and has additional properties: Value as OptionSet and OptionSetFields as OptionSetField[]. Each OptionSetField has Value and IsValidBit properties as bool. Setting them will update the underlying Value as OptionSet inside the OptionSetValue object.

  • The EnumValue is derived from BaseComplexTypeValue and has some additional properties: Value as int, ValueString as string, ValueStrings as string[], IsOptionSet as bool. This type describes an enumeration instance and it covers both: simple enumerations ans option set enumerations.

Note: In case of Enumeration values an extra method call is required to transform the integer value into an instance of EnumValue. For this method it is important to know the data type node id:

DataValueEx dataValue = m_session.Read(readValueId);

// attempt to convert the integer value read from node to an EnumValue instance

dataValue.TryConvertToEnumValue(m_session, dataValueTypeNodeId);

Note: On the server side the enumeration values are handled as integer or subtypes of uinteger values. They can be Int32 for enumerations derived from Enumeration data type or Byte for an option set enumeration derived from Byte data type. When writing the value on the server it is necessary to pass a value of the exact type expected by the server otherwise the server will return the BadTypeMismatch status code.

There are read/write samples for complex data types in Read/Write Complex values chapter.

Create Instances of Complex Data Type

For creating instances of complex data types on client side it is recommended to use the GetDefaultValueForDatatype() method from ClientSession class.* It has the following signature:

public object GetDefaultValueForDatatype(NodeId dataType, int valueRank = ValueRanks.Scalar, int arrayLength = 0, int[] arrayDimensions = null)

Use the Complex Data Types to decide what type has the object returned by GetDefaultValueForDatatype() method and cast it to its actual type to be

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 is compatible. 
.NET Framework net461 was computed.  net462 is compatible.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
3.6.0.7137 3,355 5/21/2024
3.5.0.6910 28,727 8/2/2023
3.4.0.6625 15,639 3/17/2023
3.3.0.6548 4,542 12/14/2022
3.2.0.6453 10,731 8/4/2022
3.1.0.3358 30,051 3/8/2022
3.0.0.3152 4,752 11/26/2021