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:
Right click on the project and add the following generated files (Add → Existing Item...):
- [Namespace]Identifiers.cs
- [Namespace]Types.cs
Step 2: Add References
Select “Add Reference ...” from the context menu of the project and select
- System.Runtime.Serialization
- UnifiedAutomation.UaBase
- UnifiedAutomation.UaClient
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;
using UnifiedAutomation.UaBase;
using UnifiedAutomation.UaClient;
using ModelOrganization.ExampleModelNs;
Now we are prepared to implement a simple client using the Unified Automation .NET SDK.
Step 3: Add Namespaces
The new complex type Vector must be added to the EncodeableFactory.
static void Main(string[] args)
{
MessageContext context = new MessageContext();
context.Factory.AddEncodeableType(typeof(ModelOrganization.ExampleModelNs.Vector));
}
Step 4: 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:
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;
Step 5: 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:
NodeId complexDataId = ModelOrganization.ExampleModelNs.VariableIds.MyObject_DemoVector.ToNodeId(table);
Now we can read the variable:
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 6: Write a Variable Having a Structured Data Type
Add the following code to Main to write the Value attribute of the variable:
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:
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: 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:
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:
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:
List<StatusCode> inputArgumentErrors;
List<Variant> outputArguments;
StatusCode result = session.Call(ObjectToCallId,
MethodToCallId,
inputArguments,
out inputArgumentErrors,
out outputArguments);
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 8: Subscribe to Data Changes
We subscribe to the same node that we have read.
First we have to create a subscription:
Subscription subscription = new Subscription(session);
subscription.Create();
Then we have to add the item to Monitor to the subscription:
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:
subscription.DataChanged += new DataChangedEventHandler(Subscription_DataChanged);
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 9: 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");
MyObjectModel model = new MyObjectModel();
model.DemoVector = new Vector();
LinkModelToNode(ObjectIds.MyObject.ToNodeId(Server.NamespaceUris), model, null, null, 500);
}
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
)
{
out1 = new Vector()
{
X = in1.X + in2.X,
Y = in1.Y + in2.Y,
Z = in1.Z + in2.Z
};
return StatusCodes.Good;
}
Compile and run the servcer. 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 10: Subscribe to Events
In this step, we’re adding an event monitored item to the subscription. Add the following code to Main:
...
subscription.DataChanged += new DataChangedEventHandler(Subscription_DataChanged);
subscription.NewEvents += new NewEventsEventHandler(Subscription_NewEvents);
EventMonitoredItem tmpItem = new EventMonitoredItem(UnifiedAutomation.UaBase.ObjectIds.Server);
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);
...
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);
}
}