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.

OPC UA Live mapping to Siemens S7-1500 PLC,

More
28 Sep 2022 12:37 #11123 by support
Hello,
I am glad that it works.

I do not know what the issue with writing is, but the "main" example for Live Mapping also include writing: opclabs.doc-that.com/files/onlinedocs/QuickOpc/Latest/User%2...#Live%20Mapping%20Example.html , so it may help.

Also see opclabs.doc-that.com/files/onlinedocs/QuickOpc/Latest/User%2...voking%20the%20Operations.html .

When you wrote "Lets say I have 10 PLCs that I want to map and read and map data from.", I assume that it means that each PLC has its own embedded OPC UA server, correct?

This can probably be done in multiple way, but the expected approach is as follows (I am using the code you already posted as a starting point):

- Keep just one MachineStatus, Machine1 etc. class.
- Use just one instance of UAClientMapper.
- Create 10 instances your MachineStatus class, one for each PLC; and with each such instance, call "mapper.Map(...)", with a different mapping context, where the endpoint URL will differ and point to the proper PLC for that MachineStatus instance.

When you then will read or subscribe through the mapper, with no additional parameters all PLCs will perform the operation. The mapped properties will change accordingly, always on the right instance of MachineStatus that belongs to its "own" PLC.

If you need, inside the property setter, to know which PLC you are "on", you can either add some un-mapped property with the distinguishing value, and set it when you create the MachineStatus instance, OR you can map this property too, using the Meta-Members feature (opclabs.doc-that.com/files/onlinedocs/QuickOpc/Latest/User%2...ebframe.html#Meta-Members.html ), and it will be filled in for you during the "mapper.Map(...)" call.

Best regards

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

More
27 Sep 2022 20:35 #11122 by maxtester
Hi,
Both hints together solved my problem :)
I removed the mapping kind, so that I could see the actual value, and then I discovedred som typo in the path, by checking each part of it with the help og UAExpert.
I can now finally read and map the entire datablock. I also managed to subscribe to changes, but I have problems writing to it, with the help of mapper.WriteTarget. I think I can solve this, with some trial and error.

But I have an additional question;
Lets say I have 10 PLCs that I want to map and read and map data from.
What is the most efficient and suggested way to solve this?
Create 10 different UAMappingContext?
This would give me a datachange notification for each mappingcontext I would assume...
Is there som examples when using live mapping in a more adanced way?

Finally I must say that discovering this live mapping feature was truly a game changer!
Thanks.

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

More
27 Sep 2022 07:11 #11121 by support
Hello.

I have had a closer look at your code, and I think I could identify at least two issues that need to be addressed for start:

1. The mappings on the Machine1 class use the "ErrorMessage" mapping kind, which means that your properties will not receive the actual values, but the error message associated with it. But, that is not going to work either, because ErrorMessage requires a String member, but you have 'bool' and 'int'.

I suggest that you create two copies of these mappings. In the first copy, remove the "Kind = " parts. This will map the actual values. In the second copy, change the types to 'string', and give the properties names e.g. like AlarmError, StatusCodeError.

2. The initial path, that you obtain by parsing the browse path, uses the namespace "www.siemens.com/simatic-s7-opcua" for all its browse names (PLC_DEVICE, DataBlocksGlobal, and MachineStatus). This may be correct, or not. The way to verify the correctness is to check the namespace of the BrowseName on *each* of these nodes, e.g. in UAExpert. Do you see namespace "3" in the Browsename for all of them?

Best regards
The following user(s) said Thank You: maxtester

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

More
26 Sep 2022 17:32 #11120 by support
Actually, I can now see that you already have some "ErrorMessage" mapping in your code, but the way it is done does not appear to be functional for our purpose. Stay tuned, I will put together some better reply; later today, or tomorrow.

Regards

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

More
26 Sep 2022 17:29 #11119 by support
Hello.

I will address the actual code and screenshots you have sent in a later post, after I study them a little.

But, before that: Can you please obtain the error information? When the "Read" on the map[per "does nothing", it actually means that there were errors. And the errors can be obtained by adding additional members and map them with specific "mapping kinds" for errors. See:

- opclabs.doc-that.com/files/onlinedocs/QuickOpc/Latest/User%2...del%20in%20Live%20Mapping.html

- opclabs.doc-that.com/files/onlinedocs/QuickOpc/Latest/User%2...rame.html#Mapping%20Kinds.html

So can you please add, for example, a property of Exception type, and add the same mapping attributes as the member you are interested in has, AND an additional "Kind = UADataMappingKind.Exception" in its [UAData] attribute? When you then perform the Read, this property will contain the error information we need.

Best regards

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

More
26 Sep 2022 10:11 #11117 by maxtester
Hi,
I have been testing around with the "Boiler" OPC UA live mapping example, to try to learn how this is wired together. I have tried to adapt this to a siemens plc.
I have some issues getting the namespaces correct I believe, and when I try to issue the mapper.Read() command, my model class is not being updated.

I have the following configured in the OPC server:


I have modeled this to the following C# class:
using OpcLabs.EasyOpc.UA;
using OpcLabs.EasyOpc.UA.LiveMapping;
 
namespace opc.ua.live.mapping.models
{
    [UANamespace("http://www.siemens.com/simatic-s7-opcua")]
    [UAType]
    public class MachineStatus
    {
        [UANode(BrowsePath = "/Machine1")]
        public Machine1 Machine1 = new();
    }
 
    [UAType]
    public class Machine1
    {
        [UANode, UAData(Operations = UADataMappingOperations.ReadAndSubscribe, Kind = UADataMappingKind.ErrorMessage)]
        public bool Alarm { get; set; }
 
        [UANode, UAData(Operations = UADataMappingOperations.ReadAndSubscribe, Kind = UADataMappingKind.ErrorMessage)]
        public int StatusCode { get; set; }
    }
}

Calling the mapping is done like this:
using opc.ua.live.mapping.models;
using OpcLabs.EasyOpc.UA;
using OpcLabs.EasyOpc.UA.LiveMapping;
using OpcLabs.EasyOpc.UA.Navigation;
using OpcLabs.EasyOpc.UA.OperationModel;
 
Console.WriteLine("Quick OPC UA Live Mapper Example started!");
 
// the OPC server
UAEndpointDescriptor endpointDescriptor =
    "opc.tcp://192.168.2.2:4840";
 
var mapper = new UAClientMapper();
var machineStatus = new MachineStatus();
 
mapper.Map(machineStatus, new UAMappingContext
{
    EndpointDescriptor = endpointDescriptor,
    NodeDescriptor = new UANodeDescriptor
    {
        BrowsePath = UABrowsePath.Parse("[ObjectsFolder]/PLC_DEVICE/DataBlocksGlobal/MachineStatus", "http://www.siemens.com/simatic-s7-opcua")
    },
});
 
Console.WriteLine("Press Enter to exit...");
 
await Task.Factory.StartNew(async () =>
{
    Console.WriteLine("Reading all data of the datablock...");
    while (true)
    {
        try
        {
            mapper.Read();
            Console.WriteLine($"Current Status: {machineStatus.Machine1.StatusCode}");
        }
        catch (UAException ex)
        {
            Console.WriteLine(ex.ToString());
        }
        await Task.Delay(1000, CancellationToken.None);
    }
 
}, CancellationToken.None);
 
Console.ReadLine();
 

What I`m strugling with is getting the namespaces to be correct.
Based on the submitted screenshot of UAExpert, how should I configure the UANodeDescriptor object, when using a siemens PLC?
I have used the namespace "www.siemens.com/simatic-s7-opcua", and I have tried all possible paths with ObjectsFolder...
mapper.Map(machineStatus, new UAMappingContext
{
    EndpointDescriptor = endpointDescriptor,
    NodeDescriptor = new UANodeDescriptor
    {
        BrowsePath = UABrowsePath.Parse("[ObjectsFolder]/PLC_DEVICE/DataBlocksGlobal/MachineStatus", "http://www.siemens.com/simatic-s7-opcua")
    },
});

Also, in the "Boiler"example, the Boiler class is using "Boiler" in the namespace attribute, but I`m using "www.siemens.com/simatic-s7-opcua".
 [UANamespace("http://www.siemens.com/simatic-s7-opcua")]
    [UAType]
    public class MachineStatus
    {
        [UANode(BrowsePath = "/Machine1")]
        public Machine1 Machine1 = new();
    }


In addition, when I browse the registered namespaces in UA Expoert, I have the following entries:


I`m not sure how or if the element [1] is relevant, or where this should be inserted in the submitted code.
I`m obviously missing something here, but I cant see what.

I would very much appreciate some guidance, on what to change in my example code.
Thanks
Attachments:

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

Moderators: support
Time to create page: 0.077 seconds