UA Bundle SDK .NET  2.3.3.343
 All Classes Namespaces Functions Variables Enumerations Enumerator Properties Events Modules Pages
.NET SDK Demo Server

The ANSI C SDK Demo Server is a collection of examples for developing different features of an OPC UA server and contains the Unified Automation Demo address space with nodes in the Demo folder.

The source code for the Unified Automation .NET based OPC UA demo server is included in the SDK. It contains an example (LargeArrayNodeManager.cs) for implementing information without the toolkit functionality.

The source code as a Visual Studio Solution can be found in Examples → UA Demo Server.

FileModel Example

The FileModel class implements support for the FileType object on the server side, which can be used to give an OPC UA client access to a file managed by the OPC UA server.

The .NET OPC UA Demo Server contains an example in the Address Space in the folder Objects → Demo → 014_Files. The example code can be found in the Visual Studio Solution for the Demo Server in the file Demo → DemoNodeManager.File.cs.

The following paragraphs describe the method SetupFileObject which creates a new file object. It takes the following arguments:

parentId
The NodeId of the parent node. The created File object will be referenced by an Organizes reference.
browseName
The browse name of the new File object. The browseName is also used to create the NodeId of the File object.
filePath
The file path on the disk.
description
Used for the description attribute of the new file object.

Create the OPC UA Nodes

NodeId objectId = new NodeId("Demo.Files." + browseName, DefaultNamespaceIndex);
CreateObjectSettings settings = new CreateObjectSettings()
{
ParentNodeId = parentId,
ReferenceTypeId = ReferenceTypeIds.Organizes,
RequestedNodeId = objectId,
BrowseName = new QualifiedName(browseName, DefaultNamespaceIndex),
TypeDefinitionId = ObjectTypeIds.FileType
};
if (description != null)
{
settings.Description = new LocalizedText(description);
}
CreateObject(Server.DefaultRequestContext, settings);

Create the Model Class

UnifiedAutomation.UaServer.FileModel

FileModel file = new FileModel();

Set the FileModel Properties

file.Writable = true;
file.UserWritable = true;
file.MaxFileSize = 10000;
file.FileOnDisk = new System.IO.FileInfo(filePath);

Create a Default File if None Exists

if (!file.FileOnDisk.Exists)
{
using (var ostrm = file.FileOnDisk.Open(System.IO.FileMode.Create, System.IO.FileAccess.Write, System.IO.FileShare.None))
{
byte[] bytes = System.Text.Encoding.UTF8.GetBytes(
"This file is used for demonstrating the OPC UA FileType.\n"
+ "You can call the Open method of the TextFile object with Mode argument 1 for ReadOnly.\n"
+ "You have to remember the returned FileHandle argument.\n"
+ "This FileHandle can be used for reading the file by calling the Read method and closing\n"
+ "the file by calling the Close method.");
ostrm.Write(bytes, 0, bytes.Length);
}
file.FileOnDisk.Refresh();
}

Set the File Size Depending on the File Content

file.Size = (ulong)file.FileOnDisk.Length;

LinkModelToNode

UnifiedAutomation.UaServer.BaseNodeManager.LinkModelToNode

LinkModelToNode(objectId, file, null, null, 500);

Write Implementation for DiscreteItemTypes

The DemoServer Address Space contains examples of the following Subtypes of the DiscreteItemType in Demo → 010_ComplianceTest → DA Profile → DataItemDiscreteType:

  1. TwoStateDiscreteType
  2. MultiStateDiscreteType
  3. MultiStateValueDiscreteType

TwoStateDiscreteType

The DataType of an instance of TwoStateDiscreteType is Boolean. The text representing the value is stored in the properties TrueState and FalseState. So it is not necessary to prevent clients from writing values that are not allowed.

MultiStateDiscreteType

Instances of a MultiStateDiscreteType have a property called EnumStrings. The value of this property is an array of LocalizedTexts. The corresponding text for a value can be found at the array index of the value of the property. E.g. the corresponding textual representation of the value 2 can be found at the array index 2 of the value of the property.

So the allowed values for an instance of MultiStateDiscreteType are:
0 ≤ allowed value < array length.

If a client tries to write a value that is not allowed, the server has to return BadOutOfRange (see example code below). The code snippet is taken from the class EnumStringDataSource which can be found in the file Demo → ValueDataStore.cs in the Visual Studio Solution for the Demo Server.

public override StatusCode Write(int componentIndex, Variant value, StatusCode status, DateTime timestamp)
{
if (timestamp == DateTime.MinValue)
{
timestamp = DateTime.UtcNow;
}
if (componentIndex == 0)
{
UnifiedAutomation.UaBase.TypeInfo typeInfo = TypeUtils.IsInstanceOfDataType(value, TypeInfo);
if (typeInfo == null)
{
return StatusCodes.BadTypeMismatch;
}
try
{
int newValue = value.ToInt32();
if (newValue >= 0 && newValue < EnumStrings.Length)
{
SelectedIndex = newValue;
Status = status;
Timestamp = timestamp;
return StatusCodes.Good;
}
}
catch (Exception)
{
// assume out of range error.
}
return StatusCodes.BadOutOfRange;
}

MultiStateValueDiscreteType

The behavior for an instance of MultiStateValueDiscretetType is quite similar. The instances have a property called EnumValues. The value of this property contains an array of EnumValueType. I.e. the value contains an array of value-text pairs. So the server has to check wheter the value of the property contains the value to write (see example below). The code snippet is taken from the class EnumValueDataSource which can be found in the file Demo → ValueDataStore.cs in the Visual Studio Solution for the Demo Server.

public override StatusCode Write(int componentIndex, Variant value, StatusCode status, DateTime timestamp)
{
if (timestamp == DateTime.MinValue)
{
timestamp = DateTime.UtcNow;
}
if (componentIndex == 0)
{
UnifiedAutomation.UaBase.TypeInfo typeInfo = TypeUtils.IsInstanceOfDataType(value, TypeInfo);
if (typeInfo == null)
{
return StatusCodes.BadTypeMismatch;
}
try
{
long newValue = value.ToInt64();
for (int ii = 0; ii < EnumValues.Length; ii++)
{
if (EnumValues[ii].Value == newValue)
{
SelectedIndex = ii;
Status = status;
Timestamp = timestamp;
return StatusCodes.Good;
}
}
}
catch (Exception)
{
// assume out of range error.
}
return StatusCodes.BadOutOfRange;
}

LocalizedData Example

The Demo Server contains an example for a variable containing localized data in the folder Objects → Demo → 011_UnicodeTest. The variable IDS_HELLO_WORLD has localizations for display name, description, and the value in English, German, and French.

The variable IDS_HELLO_WORLD is created in the method SetupLocalizedVariable which can be found in the file Demo → DemoNodeManager.cs in the Visual Studio Solution for the Demo Server.

The individual translations have to be added to the ResourceManager as shown in the following code snippet.

UnifiedAutomation.UaServer.ResourceManager

Server.ResourceManager.Add("IDS_HELLO_WORLD", "en-US", "Hello World");
Server.ResourceManager.Add("IDS_HELLO_WORLD", "de-DE", "Hallo Welt");
Server.ResourceManager.Add("IDS_HELLO_WORLD", "fr-CA", "Bonjour tout le monde");
Server.ResourceManager.Add("IDS_LIGHT_RED", "en-US", "Red");
Server.ResourceManager.Add("IDS_LIGHT_RED", "de-DE", "Rot");
Server.ResourceManager.Add("IDS_LIGHT_RED", "fr-CA", "Rouge");
Server.ResourceManager.Add("IDS_LIGHT_GREEN", "en-US", "Green");
Server.ResourceManager.Add("IDS_LIGHT_GREEN", "de-DE", "Grün");
Server.ResourceManager.Add("IDS_LIGHT_GREEN", "fr-CA", "Verte");

When in-memory nodes are read, the Translate method of the ResourceManager is called by the SDK automatically.

To test the translations, set the configuration parameter General.LocaleId of UaExpert to de-DE, connect to the .NET Demo Server and read the Variable. The german localizations of DisplayName, Description and Value will show up (see screenshot).

ModelChangeEvent Example

ModelChange events can be fired by the server if nodes or references are added or deleted during runtime. A client that subscribes to ModelChanegEvents is able to detect these changes to the information model. E.g. UaExpert can refresh the information model tree view automatically so that the user doesn’t need to call rebrowse manually.

The folder 008_DynamicNodes in the address space contains a property called NodeVersion. This property is required for firing ModelChangeEvents for a node. I.e. A node shall be added to the folder, so the property is required for the folder. In this case the property is imported by the XML file.

The OPC UA specification defines the method CreateDynamicNode. The .NET implementation is the method DoCreateDynamicNode which can be found in the file DemoNodeManager.Methods.cs. The following code snippet shows how to add the node.

string name = "DynamicNode";
CreateVariableSettings settings = new CreateVariableSettings()
{
ParentNodeId = new NodeId("Demo.DynamicNodes", DefaultNamespaceIndex),
ReferenceTypeId = ReferenceTypeIds.HasComponent,
RequestedNodeId = new NodeId("Demo.DynamicNodes." + name, DefaultNamespaceIndex),
BrowseName = new QualifiedName(name, DefaultNamespaceIndex),
DisplayName = name,
AccessLevel = AccessLevels.CurrentReadOrWrite,
DataType = DataTypeIds.UInt32,
ValueRank = ValueRanks.Scalar,
Historizing = false,
TypeDefinitionId = VariableTypeIds.BaseDataVariableType,
ValueType = NodeHandleType.ExternalPush,
ValueData = new DataSourceAddress(m_dynamicNode, 0)
};
CreateVariable(Server.DefaultRequestContext, settings);

In this example, the ModelChangeEvent is not fired directly from the method which creates the dynamic node. Instead the method AfterAddReference (which can be found in the file DemoNodeManager.cs) is used, which is called by the SDK after a reference has been added. The UpdateNodeVersion method tries to update the NodeVersion property and to fire the event.

First, the method checks the UserData wheter node versioning is supported.

NodeWithVersioning data = node.UserData as NodeWithVersioning;
if (data == null)
{
return false;
}

If so, the node version is updated.

lock (m_datasources)
{
DataVariableDataSource datasource = data.Source;
int version = datasource.Value.ToInt32();
version++;
datasource.Value = version.ToString();
ReportChange(context, datasource, 0);
}

Finally, the event is fired.

GenericEvent e = new GenericEvent(Server.FilterManager);
e.Initialize(
null,
ObjectTypeIds.GeneralModelChangeEventType,
ObjectIds.Server,
BrowseNames.Server,
"The address space has changed.");
ModelChangeStructureDataType[] changes = new ModelChangeStructureDataType[1];
changes[0] = new ModelChangeStructureDataType();
changes[0].Affected = node.NodeId;
changes[0].AffectedType = node.NodeId;
changes[0].AffectedType = (instance != null) ? instance.TypeDefinitionId : null;
changes[0].Verb = (byte)verb;
e.Set(BrowseNames.Changes, new Variant(changes));
// report the event.
Server.ReportEvent(e);