Professional OPC
Development Tools

logos

Online Forums

Technical support is provided through Support Forums below. Anybody can view them; you need to Register/Login to our site (see links in upper right corner) in order to Post questions. You do not have to be a licensed user of our product.

Please read Rules for forum posts before reporting your issue or asking a question. OPC Labs team is actively monitoring the forums, and replies as soon as possible. Various technical information can also be found in our Knowledge Base. For your convenience, we have also assembled a Frequently Asked Questions page.

Do not use the Contact page for technical issues.

Read, Write and Subscribe to data change of complex data types

More
13 Feb 2018 07:24 - 13 Feb 2018 07:24 #6042 by support
QuickOPC version 2018.1 (released today) has support for OPC UA Complex Data, and it can decode and encode the extension objects automatically.

More information:
Best regards
Last edit: 13 Feb 2018 07:24 by support.

Please Log in or Create an account to join the conversation.

More
07 Apr 2017 07:14 #5087 by support
The code is there - it is in the GenericSensor class, in Boiler.cs. The relevant part looks like this:
        [UANode, UAData(Operations = UADataMappingOperations.ReadAndSubscribe)] // no OPC writing
        public double Output
        {
            get { return _output; } 
            set
            {
                _output = value;
                Console.WriteLine("Sensor \"{0}\" output is now {1}.", NodeDescriptor, value);
            }
        }

After the objects are mapped to OPC, when the value changes, QuickOPC sets the corresponding property in the mapped object. So here, the setter of the GenericSensor.Output property is called, with a new value. The example just prints the information to the console, but you can write code that does whatever you want with the value.

See e.g. the explanation in opclabs.doc-that.com/files/onlinedocs/QuickOpc/Latest/User%2...#Live%20Mapping%20Example.html .

I hope this helps

Please Log in or Create an account to join the conversation.

More
06 Apr 2017 10:23 #5086 by vinaypatel.ce@gmail.com
It worked! Thank You.

One think I don’t understand with this example is, after subscription application start printing data changes for 30 second but from where? There is nothing written to print it. And how can I catch these changes in my code?

Please refer screenshot for visual.
Attachments:

Please Log in or Create an account to join the conversation.

More
05 Apr 2017 13:10 #5076 by support
There is a bug in this example in QuickOPC version 2016.2, I apologize. It is already corrected in a (not yet released) version 2017.1.

The '#' character in the browse paths need to be escaped by a '&'.
A correction is needed at two places.

The beginning of the Main method should therefore look like this:
            Console.WriteLine();
            Console.WriteLine("Mapping our data structures to OPC...");
            var mapper = new UAClientMapper();
            var boiler1 = new Boiler();
            mapper.Map(boiler1, new UAMappingContext
            {
                EndpointDescriptor = "http://opcua.demo-this.com:51211/UA/SampleServer",   // the OPC server
                // 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(
                    "http://opcua.demo-this.com:51211/UA/SampleServer",
                    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.
            }
Regards
The following user(s) said Thank You: vinaypatel.ce@gmail.com

Please Log in or Create an account to join the conversation.

More
05 Apr 2017 10:30 #5072 by vinaypatel.ce@gmail.com
When I’m trying to run example, you pointed I’m getting below error. Please refer attachment for visual.


"OPC-UA browse path format error: The character prefixing the reference type is invalid. It should be either '/', '.', or '<'.
The string to be parsed: "[ObjectsFolder]/Boilers/Boiler #1".
Symbolic code: InvalidReferenceTypeCharacter."
Attachments:

Please Log in or Create an account to join the conversation.

More
05 Apr 2017 10:28 #5071 by vinaypatel.ce@gmail.com
When I’m trying to run example, you pointed I’m getting below error. Please refer attachment for visual.

"OPC-UA browse path format error: The character prefixing the reference type is invalid. It should be either '/', '.', or '<'.
The string to be parsed: "[ObjectsFolder]/Boilers/Boiler #1".
Symbolic code: InvalidReferenceTypeCharacter."
Attachments:

Please Log in or Create an account to join the conversation.

More
05 Apr 2017 08:08 #5069 by support
Yes, you will need to write the client code based on how the OPC server organizes the data in its address space/information model.

Example for case 1) is not different from accessing any single variable. E.g. opclabs.doc-that.com/files/onlinedocs/QuickOpc/Latest/User%2...s%20of%20OPC%20UA%20Nodes.html .

Example for case 2): When you install the product and open the Visual Studio solution with C# examples, you will find it in the Console/UAConsoleLiveMapping project. Below are its major parts:
    // 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 { return _output; } 
            set
            {
                _output = value;
                Console.WriteLine("Sensor \"{0}\" output is now {1}.", NodeDescriptor, value);
            }
        }
 
        private double _output;
    }
 
    [UAType]
    class GenericActuator
    {
        [UANode, UAData(Operations = UADataMappingOperations.Write)]    // generic actuator input is not readable
        public double Input { get; set; }
    }
}

and
static void Main()
        {
            Console.WriteLine();
            Console.WriteLine("Mapping our data structures to OPC...");
            var mapper = new UAClientMapper();
            var boiler1 = new Boiler();
            mapper.Map(boiler1, new UAMappingContext
            {
                EndpointDescriptor = "http://opcua.demo-this.com:51211/UA/SampleServer",   // the OPC server
                // 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(
                    "http://opcua.demo-this.com:51211/UA/SampleServer",
                    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: {0}", boiler1.Drum.LevelIndicator.Output);
 
            Console.WriteLine();
            Console.WriteLine("Writing new setpoint value...");
            boiler1.LevelController.SetPoint = 50.0;
            Debug.Assert(boiler1.LevelController != 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();
        }
 
The following user(s) said Thank You: vinaypatel.ce@gmail.com

Please Log in or Create an account to join the conversation.

More
04 Apr 2017 09:43 #5068 by vinaypatel.ce@gmail.com
Thanks again for your quick response.

My question is generic and I'm not targeting any specific OPC server at this point. I’m evaluating QuickOPC functionalities. But Initially it will be start with Beckhoff and B&R servers.

As you mentioned in your response, does it mean we can’t control representation of user defined structure in address space and we have to write client code based on what will OPC server present to us?

If yes, would you please suggest and show me examples for case 1 and 2 with OPCLabs sample server.

Please Log in or Create an account to join the conversation.

More
04 Apr 2017 06:15 - 04 Apr 2017 06:17 #5065 by support
I understand what you need. But it is impossible to answer your question without knowing how the OPC Server actually represents the data you have laid out.

There is nothing (besides conventions, usefulness concerns, sector standardization efforts etc.) that prescribes to the OPC Server how the data structures you have given will be represented in OPC UA address space. It can be represented e.g.

1) by a set of seemingly unrelated variables
2) by a complex object with nodes structured so that they represent the elements of your type
3) by a single variable carrying a custom data type
4) or somehow else

Cases 1) and 2) are supported by QuickOPC, including Live Mapping (in fact, Live Mapping is specifically designed to cover case 2) excellently). Case 3) is not currently supported.

Is the OPC UA server in the device itself, or is it a separate box? What device vendor/model and OPC UA Server/model are you using?

Or is your question generic and you do not know which device or OPC server you are targeting?

Best regards
Last edit: 04 Apr 2017 06:17 by support.
The following user(s) said Thank You: vinaypatel.ce@gmail.com

Please Log in or Create an account to join the conversation.

More
04 Apr 2017 06:02 - 04 Apr 2017 06:16 #5064 by vinaypatel.ce@gmail.com
Thanks for the information.

Let me clear out my requirements again with example and what I’m trying to understand. Let me know if I’m misunderstood anything here and please advise to it.

By mean of complex types, I’m saying user defined structures in PLC. Below are the examples. TComplexStruct may have array of string or array of other structure type based on requirements.
TYPE TSimpleStruct :
STRUCT
        lrealVal: LREAL := 1.23;
        dintVal1: DINT := 120000;
END_STRUCT
END_TYPE
 
TYPE TComplexStruct :
STRUCT
        simpleStruct1: TSimpleStruct;
	intVal : INT:=1200;
        dintArr: ARRAY[0..3] OF DINT:= 1,2,3,4;
        boolVal: BOOL := FALSE;
        byteVal: BYTE:=10;
        stringVal : STRING(5) := 'hallo';
END_STRUCT
END_TYPE
I want to understand that is it possible to read, write and monitor data changes on TComplexStruct? And again does both ways, procedural coding and live mapping support it?
Last edit: 04 Apr 2017 06:16 by support.

Please Log in or Create an account to join the conversation.

Moderators: support
Time to create page: 0.098 seconds