UaModeler  1.6.5.472
HowTo Use the Generated Files in a Visual Studio Project

This example will describe how to integrate the code generated in HowTo Create a New Project With a Structured Data Type into a simple client based on the Unified Automation .NET SDK. This HowTo also uses the default application generated in Step 11 of the previous HowTo.

Step 1: Add Files to a Visual Studio Project

Create a new Visual Studio Studio project with the following settings:

studio_net_4.png
Project Settings

Right click on the project and add the following generated files (Add → Existing Item...):

  • [Namespace]Identifiers.cs
  • [Namespace]Types.cs
studio_net_1.png

Step 2: Add References

Select “Add Reference ...” from the context menu of the project and select

  • System.Runtime.Serialization
studio_net_2.png

For .NET SDK 2.x you need to add the Unified Automation assemblies.

  • UnifiedAutomation.UaBase
  • UnifiedAutomation.UaClient
Note
Sometimes the assemblies UnifiedAutomation.UaBase and UnifiedAutomation.UaClient do not show up in the list of registered assemblies. Then you have to search for them manually.
Click on “Browse” to search for the assemblies. The SDK installation directory contains a folder named “assemblies”, which itself contains several folders named “NET_[version]”. Add the assemblies located in the folder corresponding to the .NET framework you are using.

For .NET SDK 3.x you need to add the UnifiedAutomation NuGet packages.

  • Right-click the project in the Solution Explorer and click "Manage NuGet Packages...".
  • Add UnifiedAutomation to Package source. Click the gear wheel for the settings.
  • Click the '+' symbol to add package source.
  • Set the name for the package source and the location of the NuGet packages.
  • Select UnifiedAutomation as package source.
  • Select the Browse tab for the selecting the Unified Automation NuGet packages.
  • Install
    • UnifiedAutomation.UaBase
    • UnifiedAutomation.UaBase.Windows
    • UnifiedAutomation.UaClient
studio_net_5.png
Add UnifiedAutomation to Package source


studio_net_6.png
Specify name and location of NuGet Packages

Step 3: Add Namespaces

Add the following lines to the file Program.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
// New code begins
using UnifiedAutomation.UaBase;
using UnifiedAutomation.UaClient;
using ModelOrganization.ExampleModelNs;
// New code ends

Now we are prepared to implement a simple client using the Unified Automation .NET SDK.

Step 4: Add the Structured Data Type

The new complex type Vector must be added to the EncodeableFactory.

static void Main(string[] args)
{
// New code begins
// Add structured type
MessageContext context = new MessageContext();
context.Factory.AddEncodeableType(typeof(ModelOrganization.ExampleModelNs.Vector));
// New code ends
}

Step 5: Connect to Server

First we create a new session and connect to the server using a hard-coded non-secure endpoint. Add the following code to Main:

// Connect
Session session = new Session();
session.Connect("opc.tcp://localhost:48030", SecuritySelection.None);

To map the NamespaceIndex from the server to the client and vice versa, we need the NamespaceTable of the Session.

NamespaceTable table = session.NamespaceUris; // Add this line

Step 6: Read a Variable Having a Structured Data Type

We want to read a variable that has the DataType Vector.

Using the NamespaceTable and the generated file [Namespace]Identifiers.cs (in this example “ExampleModelNsIdentifiers.cs”) we can determine the NodeId of the variable to read. Add the following code to Main:

// Get NodeId to read and write
NodeId complexDataId = ModelOrganization.ExampleModelNs.VariableIds.MyObject_DemoVector.ToNodeId(table);

Now we can read the variable:

// Read
Vector vector;
ReadValueIdCollection nodesToRead = new ReadValueIdCollection();
ReadValueId readValueId = new ReadValueId()
{
NodeId = complexDataId,
AttributeId = Attributes.Value
};
nodesToRead.Add(readValueId);
List<DataValue> results = session.Read(nodesToRead);
if (results.ToArray().Length == 1)
{
DataValue value = results[0];
ExtensionObject extOb = (ExtensionObject)value.Value;
if (extOb != null)
{
vector = ExtensionObject.GetObject<Vector>(extOb);
}
}

Step 7: Write a Variable Having a Structured Data Type

Add the following code to Main to write the Value attribute of the variable:

// Write
vector = new Vector()
{
X = 12.0,
Y = 24.0,
Z = 42.0
};
WriteValueCollection nodesToWrite = new WriteValueCollection();
nodesToWrite.Add(new WriteValue()
{
NodeId = complexDataId,
AttributeId = Attributes.Value,
Value = new DataValue()
{
Value = vector
}
});
session.Write(nodesToWrite);

Read the Value attribute again:

// Read again
results = session.Read(nodesToRead);
if (results.ToArray().Length == 1)
{
DataValue value = results[0];
ExtensionObject extOb = (ExtensionObject)value.Value;
if (extOb != null)
{
vector = ExtensionObject.GetObject<Vector>(extOb);
}
}

Step 8: Call a Method

Calling a method is described in the Unified Automation .NET Client SDK Call – Call Method tutorial in detail. In this step you can see how to use the custom structured DataType and how to get the NodeIds from the generated files.

First we need the NodeIds of the object containing the method and the method itself:

// Call method
NodeId ObjectToCallId = ModelOrganization.ExampleModelNs.ObjectIds.MyObject.ToNodeId(table);
NodeId MethodToCallId = ModelOrganization.ExampleModelNs.MethodIds.MyObject_VectorAdd.ToNodeId(table);

Then we set the input arguments for the method:

// Set input arguments
List<Variant> inputArguments = new VariantCollection(2);
Vector vectorM = new Vector()
{
X = 1,
Y = 2,
Z = 3
};
inputArguments.Add(new Variant(vectorM));
vector = new Vector()
{
X = 3,
Y = 2,
Z = 6
};
inputArguments.Add(new Variant(vector));

Finally, the method is called:

// Call the method
List<StatusCode> inputArgumentErrors;
List<Variant> outputArguments;
StatusCode result = session.Call(ObjectToCallId,
MethodToCallId,
inputArguments,
out inputArgumentErrors,
out outputArguments);
// Get the result
if (result.IsGood())
{
foreach (Variant value in outputArguments)
{
Console.WriteLine(value);
if (value.TypeInfo == TypeInfo.Scalars.ExtensionObject)
{
ExtensionObject extensionObject = value.ToExtensionObject();
Vector sum = ExtensionObject.GetObject<Vector>(extensionObject);
}
}
}

Step 9: Subscribe to Data Changes

We subscribe to the same node that we have read.

First we have to create a subscription:

//Subscribe
// Create Subscription
Subscription subscription = new Subscription(session);
subscription.Create();

Then we have to add the item to Monitor to the subscription:

// Add MonitoredItem
List<MonitoredItem> monitoredItems = new List<MonitoredItem>();
monitoredItems.Add(new DataMonitoredItem(complexDataId)
{
UserData = "Vector"
});
subscription.CreateMonitoredItems(monitoredItems);

Add the event handler and wait for data changes:

// Add EventHandler
subscription.DataChanged += new DataChangedEventHandler(Subscription_DataChanged);
//just wait for Data Changes
Console.WriteLine("Press ESC to stop");
do
{
while (!Console.KeyAvailable)
{
System.Threading.Thread.Sleep(10);
}
} while (Console.ReadKey(true).Key != ConsoleKey.Escape);

Finally we implement the callback function. The code shows how to get access to the Vector. Add Subscription_DataChanged to Program.cs:

static void Subscription_DataChanged(Subscription subscription, DataChangedEventArgs e)
{
foreach (DataChange dataChange in e.DataChanges)
{
if (dataChange.MonitoredItem.UserData.Equals("Vector"))
&& dataChange.Value.Value != null)
{
Console.WriteLine(dataChange.Value);
Vector vector = (Vector)((ExtensionObject)dataChange.Value.Value).Body;
Console.WriteLine(vector);
}
}
}

Step 10: Prepare Server Application and Connect to Server

To test our client, we have to complete the default server application generated in the previous HowTo.

Double-click on the generated file [Namespace].csproj (in this case ExampleProjectNs.csproj) and open the file [Namespace]NodeManager.cs (here ExampleModelNsNodeManager.cs).

Add the following code to the method Startup:

public override void Startup()
{
try
{
Console.WriteLine("Starting ExampleModelNsNodeManager.");
DefaultNamespaceIndex = AddNamespaceUri("http://yourorganisation.org/ExampleModel/");
Console.WriteLine("Loading the ExampleModelNs Model.");
ImportUaNodeset(Assembly.GetEntryAssembly(), "examplemodel.xml");
// New code begins
MyObjectModel model = new MyObjectModel();
model.DemoVector = new Vector();
LinkModelToNode(ObjectIds.MyObject.ToNodeId(Server.NamespaceUris), model, null, null, 500);
// New code ends
}
catch (Exception e)
{
Console.WriteLine("Failed to start ExampleModelNsNodeManager " + e.Message);
}
}

The method has to be implemented in the file MyObjectTypeMethods.cs:

public StatusCode VectorAdd(
RequestContext context,
MyObjectModel model,
Vector in1,
Vector in2,
out Vector out1
)
{
// New code begins
out1 = new Vector()
{
X = in1.X + in2.X,
Y = in1.Y + in2.Y,
Z = in1.Z + in2.Z
};
return StatusCodes.Good;
// New code ends
}

Compile and run the server. Then compile and run the client. You should see the following output:

{4|4|9}
{12|24|42}
ModelOrganization.ExampleModelNs.Vector
{12|24|42}
ModelOrganization.ExampleModelNs.Vector
{12|24|42}
ModelOrganization.ExampleModelNs.Vector
{12|24|42}
ModelOrganization.ExampleModelNs.Vector
{12|24|42}
ModelOrganization.ExampleModelNs.Vector
...

The first line is the method output, i.e. the sum of the two vectors.

The following lines show the current Value attribute of the monitored item. To verify that data changes are actually displayed, connect to the server with another client and write the value.

Step 11: Subscribe to Events

In this step, we’re adding an event monitored item to the subscription. Add the following code to Main:

...
// Add EventHandler
subscription.DataChanged += new DataChangedEventHandler(Subscription_DataChanged);
// New code begins
subscription.NewEvents += new NewEventsEventHandler(Subscription_NewEvents);
// Add EventMonitoredItem
EventMonitoredItem tmpItem = new EventMonitoredItem(UnifiedAutomation.UaBase.ObjectIds.Server);
//Register Event Field
tmpItem.Filter.SelectClauses.Add(new QualifiedName(ModelOrganization.ExampleModelNs.BrowseNames.Vector, (ushort)subscription.Session.NamespaceUris.IndexOf(ModelOrganization.ExampleModelNs.Namespaces.ExampleModelNs)));
monitoredItems.Add(tmpItem);
subscription.CreateMonitoredItems(monitoredItems);
// New code ends
...

Now we implement the callback function. The code shows how to get access to the Vector. Add Subscription_NewEvents to Program.cs:

static void Subscription_NewEvents(Subscription subscription, NewEventsEventArgs e)
{
foreach (NewEvent newEvent in e.Events)
{
ItemEventFilter filter = newEvent.MonitoredItem.Filter;
NodeId eventType = filter.GetValue<NodeId>(newEvent.Event, UnifiedAutomation.UaBase.BrowseNames.EventType, NodeId.Null);
QualifiedName browseName = new QualifiedName(ModelOrganization.ExampleModelNs.BrowseNames.Vector, (ushort)subscription.Session.NamespaceUris.IndexOf(ModelOrganization.ExampleModelNs.Namespaces.ExampleModelNs));
Vector vector = filter.GetValue<Vector>(newEvent.Event, browseName, null);
}
}