.NET Based OPC UA Client/Server SDK  3.3.0.530
Setup

Create the Server

First, the server manager is started. This step is very similar to the Getting Started example.

static void Main(string[] args)
{
try
{
ApplicationLicenseManager.AddProcessLicenses(System.Reflection.Assembly.GetExecutingAssembly(), "License.lic");
// Start the server.
Server = new TestServerManager();
ApplicationInstanceBase.Default.AutoCreateCertificate = true;
#if NETFRAMEWORK
ApplicationInstanceBase.Default.SecurityProvider = new WindowsSecurityProvider();
#else
ApplicationInstanceBase.Default.SecurityProvider = new BouncyCastleSecurityProvider();
#endif
ApplicationInstanceBase.Default.Start(Server, null, Server);
foreach (var address in ApplicationInstanceBase.Default.ApplicationSettings.BaseAddresses)
{
Console.WriteLine($"Endpoint URL: {address}");
}

Create the Machine Model

Next, the machine model will be instantiated. The model contains the whole machine including the heater, the job management, etc. It was created by the UaModeler and extended by manual written code to breathe life into it. The following section will take a closer look how to add functionality to the model.

GlassTemperingMachineModel machine = new GlassTemperingMachineModel();

Create the Model Thread

The models are all about managing state. Hence, it is crucial to serialize memory access. Unlike LinkModelToNode which is using mutex locking, BindModel is using a synchronization context to access the properties and methods of the model. The SDK ships with a very simple Synchronization context implementation, which is based on a single thread. All access is to the model will happen on this model thread.

var synchronizationContext = new SingleThreadSynchronizationContext();
synchronizationContext.Start();

Bind the Model

To Bind the model to the server, we only need the NodeId of our GlassTemperingMachine, which we can access through a UaModeler generated variable, the instance of the GlassTemperingMachine itself, the synchronization context and a method to get the node creation settings. The latter will be described in the collection section. The method will than traverse the whole model tree and attach the necessary event handler and create the needed hooks. The binding will happen on the synchronization context.

NodeId instanceId = ObjectIds.Machines_GlassTemperingMachine.ToNodeId(Server.NamespaceUris);
Server.RootNodeManager.BindModel(instanceId, machine, CreateInstanceSettingsFor, synchronizationContext);

Connect the Simulation

The simulation of the underling hardware will not happen in the model itself, but in a class called HardwareSimulation. The IO handling will happen through dictionaries that are passed between the HardwareSimulation and the GlassTemperingMachine. The method RunAsync will execute passed lambda on the model thread, so we take care that all model access will happen on the model thread.

var simulation = new HardwareSimulation();
var cts = new CancellationTokenSource();
var simTask = synchronizationContext.RunAsync(async () =>
{
Dictionary<string, int> outputs;
Dictionary<string, int> inputs = simulation.ProcessIO(null);
try
{
Console.WriteLine($"[{Thread.CurrentThread.ManagedThreadId}] Before ProcessIO loop");
while (!cts.IsCancellationRequested)
{
await Task.Delay(TimeSpan.FromMilliseconds(200));
outputs = machine.ProcessIO(inputs);
await Task.Delay(TimeSpan.FromMilliseconds(200));
inputs = simulation.ProcessIO(outputs);
}
}
catch (Exception ex)
{
Console.WriteLine($"Exception: {ex}");
}
});

Shutdown

Finally, the program waits for user input to exit the application.

// Block until the server exits.
Console.WriteLine("Press <enter> to exit the program.");
Console.ReadLine();
// Stop simulation
Console.WriteLine("Stop simulation");
cts.Cancel();
simTask.Wait();
// Stop model synchronization context
synchronizationContext.Stop();
// Stop the server.
Console.WriteLine("Stop server");
Server.Stop();