OPC Studio User's Guide and Reference
Installed Examples - Console - UAConsoleLiveMapping

Creates an object structure for a boiler, describes its mapping into OPC Unified Architecture server using attributes, and then performs the live mapping. Boiler data is then read, written and/or subscribed to using plain .NET object access..

The main program:

// UAConsoleLiveMapping: Creates an object structure for a boiler, describes its mapping into OPC Unified Architecture server 
// using attributes, and then performs the live mapping. Boiler data is then read, written and/or subscribed to using plain .NET 
// object access.
//
// Find all latest examples here: https://opclabs.doc-that.com/files/onlinedocs/OPCLabs-OpcStudio/Latest/examples.html .

using System;
using System.Diagnostics;
using System.Threading;
using OpcLabs.EasyOpc.UA;
using OpcLabs.EasyOpc.UA.LiveMapping;
using OpcLabs.EasyOpc.UA.LiveMapping.Extensions;
using OpcLabs.EasyOpc.UA.Navigation;
using OpcLabs.EasyOpc.UA.OperationModel;

namespace UAConsoleLiveMapping
{
    class Program
    {
        static void Main()
        {
            // the OPC server
            UAEndpointDescriptor endpointDescriptor =
                "opc.tcp://opcua.demo-this.com:51210/UA/SampleServer";
            // or "http://opcua.demo-this.com:51211/UA/SampleServer" (currently not supported)
            // or "https://opcua.demo-this.com:51212/UA/SampleServer/"
            
            Console.WriteLine();
            Console.WriteLine("Mapping our data structures to OPC...");
            var mapper = new UAClientMapper();
            var boiler1 = new Boiler();
            mapper.Map(boiler1, new UAMappingContext
            {
                EndpointDescriptor = endpointDescriptor,   
                // The NodeDescriptor below determines where in the OPC address space we want to map our data to.
                NodeDescriptor = new UANodeDescriptor
                    {
                        // '#' is a reserved character in a browse name, and must be escaped by '&' in the path below.
                        BrowsePath = UABrowsePath.Parse("[ObjectsFolder]/Boilers/Boiler &#1", "http://opcfoundation.org/UA/Boiler/")
                    },
                MonitoringParameters = 1000,  // requested sampling interval (for subscriptions)
            });

            Console.WriteLine();
            Console.WriteLine("Starting the simulation of the boiler in the server, using an OPC method call...");
            // Currently there is no live mapping for OPC methods, therefore we call the OPC method in a traditional way.
            try
            {
                EasyUAClient.SharedInstance.CallMethod(
                    endpointDescriptor,
                    UABrowsePath.Parse("[ObjectsFolder]/Boilers/Boiler &#1/Simulation", "http://opcfoundation.org/UA/Boiler/"),
                    UABrowsePath.Parse("[nsu=http://opcfoundation.org/UA/Boiler/;i=1287].Start", "http://opcfoundation.org/UA/"));
            }
            catch (UAException)
            {
                // Production code would test the current state of the simulation first, and also handle the exception here.
            }

            Console.WriteLine();
            Console.WriteLine("Reading all data of the boiler...");
            mapper.Read();
            Console.WriteLine($"Drum level is: {boiler1.Drum.LevelIndicator.Output}");

            Console.WriteLine();
            Console.WriteLine("Writing new setpoint value...");
            boiler1.LevelController.SetPoint = 50.0;
            Debug.Assert(!(boiler1.LevelController is null));
            mapper.WriteTarget(boiler1.LevelController, /*recurse:*/false);

            Console.WriteLine();
            Console.WriteLine("Subscribing to boiler data changes...");
            mapper.Subscribe(/*active:*/true);

            Thread.Sleep(30 * 1000);

            Console.WriteLine();
            Console.WriteLine("Unsubscribing from boiler data changes...");
            mapper.Subscribe(/*active:*/false);

            Console.WriteLine();
            Console.WriteLine("Press Enter to continue...");
            Console.ReadLine();
        }
    }
}
' UAConsoleLiveMapping: Creates an object structure for a boiler, describes its mapping into OPC Unified Architecture server 
' using attributes, and then performs the live mapping. Boiler data is then read, written and/or subscribed to using plain .NET 
' object access.
'
' Find all latest examples here: https://opclabs.doc-that.com/files/onlinedocs/OPCLabs-OpcStudio/Latest/examples.html .

Imports System.Threading
Imports OpcLabs.EasyOpc.UA
Imports OpcLabs.EasyOpc.UA.LiveMapping
Imports OpcLabs.EasyOpc.UA.LiveMapping.Extensions
Imports OpcLabs.EasyOpc.UA.Navigation
Imports OpcLabs.EasyOpc.UA.OperationModel

Friend Class Program
    Shared Sub Main()
        ' Define which server we will work with.
        Dim endpointDescriptor As UAEndpointDescriptor =
                "opc.tcp://opcua.demo-this.com:51210/UA/SampleServer"
        ' or "http://opcua.demo-this.com:51211/UA/SampleServer" (currently not supported)
        ' or "https://opcua.demo-this.com:51212/UA/SampleServer/"

        Console.WriteLine()
        Console.WriteLine("Mapping our data structures to OPC...")
        Dim mapper = New UAClientMapper()
        Dim boiler1 = New Boiler()
        ' The NodeDescriptor below determines where in the OPC address space we want to map our data to.
        ' '#' is a reserved character in a browse name, and must be escaped by '&' in the path below.
        mapper.Map(boiler1, New UAMappingContext With {
                      .EndpointDescriptor = endpointDescriptor,
                      .NodeDescriptor = New UANodeDescriptor With {
                      .BrowsePath = UABrowsePath.Parse("[ObjectsFolder]/Boilers/Boiler &#1", "http://opcfoundation.org/UA/Boiler/")},
                      .MonitoringParameters = 1000}) ' requested sampling interval (for subscriptions) -  local OPC server

        Console.WriteLine()
        Console.WriteLine("Starting the simulation of the boiler in the server, using an OPC method call...")
        ' Currently there is no live mapping for OPC methods, therefore we call the OPC method in a traditional way.
        Try
            EasyUAClient.SharedInstance.CallMethod(
                endpointDescriptor,
                UABrowsePath.Parse("[ObjectsFolder]/Boilers/Boiler &#1/Simulation", "http://opcfoundation.org/UA/Boiler/"),
                UABrowsePath.Parse("[nsu=http://opcfoundation.org/UA/Boiler/;i=1287].Start", "http://opcfoundation.org/UA/"))
        Catch e1 As UAException
            ' Production code would test the current state of the simulation first, and also handle the exception here.
        End Try

        Console.WriteLine()
        Console.WriteLine("Reading all data of the boiler...")
        mapper.Read()
        Console.WriteLine("Drum level is: {0}", boiler1.Drum.LevelIndicator.Output)

        Console.WriteLine()
        Console.WriteLine("Writing new setpoint value...")
        boiler1.LevelController.SetPoint = 50.0
        Debug.Assert(boiler1.LevelController IsNot Nothing)
        mapper.WriteTarget(boiler1.LevelController, False) 'recurse:

        Console.WriteLine()
        Console.WriteLine("Subscribing to boiler data changes...")
        mapper.Subscribe(True) 'active:

        Thread.Sleep(30 * 1000)

        Console.WriteLine()
        Console.WriteLine("Unsubscribing from boiler data changes...")
        mapper.Subscribe(False) 'active:

        Console.WriteLine()
        Console.WriteLine("Press Enter to continue...")
        Console.ReadLine()
    End Sub
End Class

 

The Boiler class:

//
// Find all latest examples here: https://opclabs.doc-that.com/files/onlinedocs/OPCLabs-OpcStudio/Latest/examples.html .

using System;
using OpcLabs.BaseLib.LiveMapping;
using OpcLabs.EasyOpc.UA;
using OpcLabs.EasyOpc.UA.LiveMapping;

namespace UAConsoleLiveMapping
{
    // The Boiler and its constituents are described in our application domain terms, the way we want to work with them.
    // Attributes are used to describe the correspondence between our types and members, and OPC nodes.

    // This is how the boiler looks in OPC address space:
    //  - Boiler #1
    //      - CC1001                    (CustomController)
    //          - ControlOut
    //          - Description
    //          - Input1
    //          - Input2
    //          - Input3
    //      - Drum1001                  (BoilerDrum)
    //          - LIX001                (LevelIndicator)
    //              - Output
    //      - FC1001                    (FlowController)
    //          - ControlOut
    //          - Measurement
    //          - SetPoint
    //      - LC1001                    (LevelController)
    //          - ControlOut
    //          - Measurement
    //          - SetPoint
    //      - Pipe1001                  (BoilerInputPipe)
    //          - FTX001                (FlowTransmitter)
    //              - Output
    //      - Pipe1002                  (BoilerOutputPipe)
    //          - FTX002                (FlowTransmitter)
    //              - Output

    [UANamespace("http://opcfoundation.org/UA/Boiler/")]
    [UAType]
    class Boiler
    {
        // Specifying BrowsePath-s here only because we have named the class members differently from OPC node names.

        [UANode(BrowsePath = "/PipeX001")]
        public BoilerInputPipe InputPipe = new BoilerInputPipe();

        [UANode(BrowsePath = "/DrumX001")]
        public BoilerDrum Drum = new BoilerDrum();

        [UANode(BrowsePath = "/PipeX002")]
        public BoilerOutputPipe OutputPipe = new BoilerOutputPipe();

        [UANode(BrowsePath = "/FCX001")]
        public FlowController FlowController = new FlowController();

        [UANode(BrowsePath = "/LCX001")]
        public LevelController LevelController = new LevelController();

        [UANode(BrowsePath = "/CCX001")]
        public CustomController CustomController = new CustomController();
    }

    [UAType]
    class BoilerInputPipe
    {
        // Specifying BrowsePath-s here only because we have named the class members differently from OPC node names.

        [UANode(BrowsePath = "/FTX001")]
        public FlowTransmitter FlowTransmitter1 = new FlowTransmitter();

        [UANode(BrowsePath = "/ValveX001")]
        public Valve Valve = new Valve();
    }

    [UAType]
    class BoilerDrum
    {
        // Specifying BrowsePath-s here only because we have named the class members differently from OPC node names.

        [UANode(BrowsePath = "/LIX001")]
        public LevelIndicator LevelIndicator = new LevelIndicator();
    }

    [UAType]
    class BoilerOutputPipe
    {
        // Specifying BrowsePath-s here only because we have named the class members differently from OPC node names.

        [UANode(BrowsePath = "/FTX002")]
        public FlowTransmitter FlowTransmitter2 = new FlowTransmitter();
    }

    [UAType]
    class FlowController : GenericController
    {
    }

    [UAType]
    class LevelController : GenericController
    {
    }

    [UAType]
    class CustomController
    {
        [UANode, UAData(Operations = UADataMappingOperations.Write)]    // not readable
        public double Input1 { get; set; }

        [UANode, UAData(Operations = UADataMappingOperations.Write)]    // not readable
        public double Input2 { get; set; }

        [UANode, UAData(Operations = UADataMappingOperations.Write)]    // not readable
        public double Input3 { get; set; }

        [UANode, UAData(Operations = UADataMappingOperations.ReadAndSubscribe)] // no OPC writing
        public double ControlOut { get; set; }

        [UANode, UAData]
        public string Description { get; set; }
    }

    [UAType]
    class FlowTransmitter : GenericSensor
    {
    }

    [UAType]
    class Valve : GenericActuator
    {
    }

    [UAType]
    class LevelIndicator : GenericSensor
    {
    }

    [UAType]
    class GenericController
    {
        [UANode, UAData(Operations = UADataMappingOperations.ReadAndSubscribe)] // no OPC writing
        public double Measurement { get; set; }

        [UANode, UAData]
        public double SetPoint { get; set; }

        [UANode, UAData(Operations = UADataMappingOperations.ReadAndSubscribe)] // no OPC writing
        public double ControlOut { get; set; }
    }

    [UAType]
    class GenericSensor
    {
        // Meta-members are filled in by information collected during mapping, and allow access to it later from your code.
        // Alternatively, you can derive your class from UAMappedNode, which will bring in many meta-members automatically.
        [MetaMember("NodeDescriptor")]
        public UANodeDescriptor NodeDescriptor { get; set; }

        [UANode, UAData(Operations = UADataMappingOperations.ReadAndSubscribe)] // no OPC writing
        public double Output
        {
            get => _output;
            set
            {
                _output = value;
                Console.WriteLine($"Sensor \"{NodeDescriptor}\" output is now {value}.");
            }
        }

        private double _output;
    }

    [UAType]
    class GenericActuator
    {
        [UANode, UAData(Operations = UADataMappingOperations.Write)]    // generic actuator input is not readable
        public double Input { get; set; }
    }
}
'
' Find all latest examples here: https://opclabs.doc-that.com/files/onlinedocs/OPCLabs-OpcStudio/Latest/examples.html .

Imports OpcLabs.BaseLib.LiveMapping
Imports OpcLabs.EasyOpc.UA
Imports OpcLabs.EasyOpc.UA.LiveMapping


' The Boiler and its constituents are described in our application domain terms, the way we want to work with them.
' Attributes are used to describe the correspondence between our types and members, and OPC nodes.

' This is how the boiler looks in OPC address space:
'  - Boiler #1
'      - CC1001                    (CustomController)
'          - ControlOut
'          - Description
'          - Input1
'          - Input2
'          - Input3
'      - Drum1001                  (BoilerDrum)
'          - LIX001                (LevelIndicator)
'              - Output
'      - FC1001                    (FlowController)
'          - ControlOut
'          - Measurement
'          - SetPoint
'      - LC1001                    (LevelController)
'          - ControlOut
'          - Measurement
'          - SetPoint
'      - Pipe1001                  (BoilerInputPipe)
'          - FTX001                (FlowTransmitter)
'              - Output
'      - Pipe1002                  (BoilerOutputPipe)
'          - FTX002                (FlowTransmitter)
'              - Output

<UANamespace("http://opcfoundation.org/UA/Boiler/"), UAType()> _
Friend Class Boiler
    ' Specifying BrowsePath-s here only because we have named the class members differently from OPC node names.

    <UANode(BrowsePath:="/PipeX001")> _
    Public InputPipe As New BoilerInputPipe()

    <UANode(BrowsePath:="/DrumX001")> _
    Public Drum As New BoilerDrum()

    <UANode(BrowsePath:="/PipeX002")> _
    Public OutputPipe As New BoilerOutputPipe()

    <UANode(BrowsePath:="/FCX001")> _
    Public FlowController As New FlowController()

    <UANode(BrowsePath:="/LCX001")> _
    Public LevelController As New LevelController()

    <UANode(BrowsePath:="/CCX001")> _
    Public CustomController As New CustomController()
End Class

<UAType()> _
Friend Class BoilerInputPipe
    ' Specifying BrowsePath-s here only because we have named the class members differently from OPC node names.

    <UANode(BrowsePath:="/FTX001")> _
    Public FlowTransmitter1 As New FlowTransmitter()

    <UANode(BrowsePath:="/ValveX001")> _
    Public Valve As New Valve()
End Class

<UAType()> _
Friend Class BoilerDrum
    ' Specifying BrowsePath-s here only because we have named the class members differently from OPC node names.

    <UANode(BrowsePath:="/LIX001")> _
    Public LevelIndicator As New LevelIndicator()
End Class

<UAType()> _
Friend Class BoilerOutputPipe
    ' Specifying BrowsePath-s here only because we have named the class members differently from OPC node names.

    <UANode(BrowsePath:="/FTX002")> _
    Public FlowTransmitter2 As New FlowTransmitter()
End Class

<UAType()> _
Friend Class FlowController
    Inherits GenericController

End Class

<UAType()> _
Friend Class LevelController
    Inherits GenericController

End Class

<UAType()> _
Friend Class CustomController
    <UANode(), UAData(Operations:=UADataMappingOperations.Write)>
    Public Property Input1 As Double

    <UANode(), UAData(Operations:=UADataMappingOperations.Write)>
    Public Property Input2 As Double

    <UANode(), UAData(Operations:=UADataMappingOperations.Write)>
    Public Property Input3 As Double

    <UANode(), UAData(Operations:=UADataMappingOperations.ReadAndSubscribe)>
    Public Property ControlOut As Double

    <UANode(), UAData()>
    Public Property Description As String
End Class

<UAType()> _
Friend Class FlowTransmitter
    Inherits GenericSensor

End Class

<UAType()> _
Friend Class Valve
    Inherits GenericActuator

End Class

<UAType()> _
Friend Class LevelIndicator
    Inherits GenericSensor

End Class

<UAType()> _
Friend Class GenericController
    <UANode(), UAData(Operations := UADataMappingOperations.ReadAndSubscribe)>
    Public Property Measurement As Double

    <UANode(), UAData()>
    Public Property SetPoint As Double

    <UANode(), UAData(Operations:=UADataMappingOperations.ReadAndSubscribe)>
    Public Property ControlOut As Double
End Class

<UAType()> _
Friend Class GenericSensor
    ' Meta-members are filled in by information collected during mapping, and allow access to it later from your code.
    ' Alternatively, you can derive your class from UAMappedNode, which will bring in many meta-members automatically.
    <MetaMember("NodeDescriptor")>
    Public Property NodeDescriptor As UANodeDescriptor

    <UANode(), UAData(Operations:=UADataMappingOperations.ReadAndSubscribe)>
    Public Property Output() As Double ' no OPC writing
        Get
            Return _output
        End Get
        Set(ByVal value As Double)
            _output = value
            Console.WriteLine("Sensor ""{0}"" output is now {1}.", NodeDescriptor, value)
        End Set
    End Property

    Private _output As Double
End Class

<UAType()> _
Friend Class GenericActuator
    <UANode(), UAData(Operations := UADataMappingOperations.Write)>
    Public Property Input As Double
End Class

 

See Also

Conceptual