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.
- Forum
- Discussions
- QuickOPC-UA in .NET
- Reading, Writing, Subscriptions
- Read, Write and Subscribe to data change of complex data types
Read, Write and Subscribe to data change of complex data types
More information:
- QuickOPC 2018.1 Released
- What's New in QuickOPC 2018.1
- OPC UA Complex Data Extension
- OPC UA Complex Data Reading
Best regards
Please Log in or Create an account to join the conversation.
[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.
- vinaypatel.ce@gmail.com
- Topic Author
- Offline
- Premium Member
- Posts: 8
- Thank you received: 0
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.
Please Log in or Create an account to join the conversation.
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 ", "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 /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.
}
Please Log in or Create an account to join the conversation.
- vinaypatel.ce@gmail.com
- Topic Author
- Offline
- Premium Member
- Posts: 8
- Thank you received: 0
"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."
Please Log in or Create an account to join the conversation.
- vinaypatel.ce@gmail.com
- Topic Author
- Offline
- Premium Member
- Posts: 8
- Thank you received: 0
"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."
Please Log in or Create an account to join the conversation.
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 ", "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 /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();
}
Please Log in or Create an account to join the conversation.
- vinaypatel.ce@gmail.com
- Topic Author
- Offline
- Premium Member
- Posts: 8
- Thank you received: 0
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.
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
Please Log in or Create an account to join the conversation.
- vinaypatel.ce@gmail.com
- Topic Author
- Offline
- Premium Member
- Posts: 8
- Thank you received: 0
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
Please Log in or Create an account to join the conversation.
- Forum
- Discussions
- QuickOPC-UA in .NET
- Reading, Writing, Subscriptions
- Read, Write and Subscribe to data change of complex data types