convertersystems / opc-ua-client

Visualize and control your enterprise using OPC Unified Architecture (OPC UA) and Visual Studio.
MIT License
397 stars 115 forks source link

Need help writing a DTL address #268

Closed gorkj83 closed 6 months ago

gorkj83 commented 7 months ago

Hello everyone,

I'm currently working on a project where I need to write a DTL (Date and Time es. DTL#1970-01-01-00:00:00) address to an OPC UA server. I'm familiar with writing other data types like strings and integers, but I'm unsure about the correct syntax and method to write a DTL address. Could someone please provide guidance or an example of how to properly write a DTL address and send it to the OPC UA server? Any help or insights would be greatly appreciated. Thank you!"

awcullen commented 7 months ago

This is a good example of reading a custom structure from an OPC Server. I added a test to read and write the Simatic DTL and have put it in branch 'SimaticIntegrationTests' of this repo.

This solution demonstrates using a tool to generate the .cs type library. The tool is available at https://github.com/quinmars/UaTypeGenerator

The major steps are: 1) Use TIA Portal to export the 'OPC UA XML' file from the project. image

2) Use UaTypeGenerator to generate the DataTypes in a .cs file. dotnet UaTypeGenerator.dll -f DTL.Nodeset2.xml -n SimaticTypeLibrary

3) Create a new NetStandard2.0 library in your project and add the generated file to it. (see this repo)

4) Use code like the following to Read and Write the variable of data type DTL.

           var readRequest = new ReadRequest
           {
               NodesToRead = new[]
               {
               new ReadValueId { AttributeId = AttributeIds.Value, NodeId = NodeId.Parse("ns=3;s=\"Data_block_1\".\"Now\"") },
               },
           };

           var readResponse = await channel.ReadAsync(readRequest);
           foreach (var result in readResponse.Results)
           {
               StatusCode.IsGood(result.StatusCode)
                   .Should().BeTrue();
           }

           // reading this node returns an ExtensionObject containing a value of type DTL 
           var v1 = readResponse.Results[0].GetValueOrDefault<SimaticTypeLibrary.DTL>();

           logger.LogInformation($"  {v1}");

           // create new DataValue for writing.  Most servers reject writing values with timestamps.

           var now = DateAndTime.Now;
           var v2 = new SimaticTypeLibrary.DTL();
           v2.YEAR = (ushort)now.Year;
           v2.MONTH = (byte)now.Month;
           v2.DAY = (byte)now.Day;
           v2.HOUR = (byte)now.Hour;
           v2.MINUTE = (byte)now.Minute;
           v2.SECOND = (byte)now.Second;
           v2.NANOSECOND = (byte)now.Nanosecond;

           var newValue = new DataValue(v2);

           var writeRequest = new WriteRequest
           {
               NodesToWrite = new[]
               {
               new WriteValue { AttributeId = AttributeIds.Value, NodeId = NodeId.Parse("ns=3;s=\"Data_block_1\".\"Now\""), Value =  newValue},
               },
           };
           var writeResponse = await channel.WriteAsync(writeRequest);
           foreach (var result in writeResponse.Results)
           {
               StatusCode.IsGood(result)
                   .Should().BeTrue();
           }
awcullen commented 7 months ago

I encountered two problems along the way.

In step 2, UaTypeGenerator did not accept the .xml file at first. Siemens wrote values for AccessLevel="257". This value exceeds the USINT size that is in the standard. I changed the values to 0.

In step 3, after making the custom library "SimaticTypeLibrary", the generated data types are very good, but I needed to add an "BinaryEncodingId" attribute to the top of each DataType class. OPC UA servers send the "BinaryEncodingId" in the response to inform the client how to decode the structure.


    /// <summary>
    /// Class for the DTL data type.
    /// </summary>
    [Workstation.ServiceModel.Ua.DataTypeId("nsu=http://www.siemens.com/simatic-s7-opcua;s=DT_DTL")]
    [Workstation.ServiceModel.Ua.BinaryEncodingId("nsu=http://www.siemens.com/simatic-s7-opcua;s=TE_DTL")]
    public class DTL : SimaticSystemStructures
    { ... }