convertersystems / opc-ua-client

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

Custom structure encodable by reflection. #159

Open awcullen opened 4 years ago

awcullen commented 4 years ago

Consider adding support for simple structures that may be encoded by reflection.

In a PLC you may create a UDT, for example:

TYPE "Vector"
VERSION : 0.1
   STRUCT
      X : LReal;
      Y : LReal;
      Z : LReal;
   END_STRUCT;

END_TYPE

Today, this library supports custom data types that implement IEncodable. I propose we add fallback support to use reflection to discover the properties and values.

using Workstation.ServiceModel.Ua;

[assembly: TypeLibrary()]
namespace PLCTypeLibrary
{
    [BinaryEncodingId("nsu=http://www.siemens.com/simatic-s7-opcua;s=TE_\"Vector\"")]
    public class Vector
    {
        public double X { get; set; }
        public double Y { get; set; }
        public double Z { get; set; }
    }
}

Reflection mode would only need to support the kind of types found in PLC UDT's.

Further, a tool could be written to extract the UDTs from your PLC program and generate the source code for a custom type library.

quinmars commented 4 years ago

This is definately interesting. An alternative to runtime reflection could be roslyn source generators. I haven't ever used them nor do I have experience in roslyn analyzers, but the user would write:

[BinaryEncodingId("nsu=http://www.siemens.com/simatic-s7-opcua;s=TE_\"Vector\"")]
partial public class Vector  // note the `partial` modifier
{
    public double X { get; set; }
    public double Y { get; set; }
    public double Z { get; set; }
}

and the source generator would generate (because Vector does not yet implement IEncodable):

partial public class Vector : IEncodable
{
    public virtual void Encode(IEncoder encoder)
    {
        encoder.PushNamespace("http://www.siemens.com/simatic-s7-opcua");
        encoder.WriteDouble("X", this.X);
        encoder.WriteDouble("Y", this.Y);
        encoder.WriteDouble("Z", this.Z);
        encoder.PopNamespace();
    }

    public virtual void Decode(IDecoder decoder)
    {
        decoder.PushNamespace("http://www.siemens.com/simatic-s7-opcua");
        X = decoder.ReadDouble("X");
        Y = decoder.ReadDouble("Y");
        Z = decoder.ReadDouble("Z");
        decoder.PopNamespace();
    }
}

The benefit would be that you do not have to pay for reflection, because everything happens during compile time.

quinmars commented 3 years ago

Further, a tool could be written to extract the UDTs from your PLC program and generate the source code for a custom type library.

I'm playing at the moment with the idea, to write an source generator that will transform NodeSet2 files into .NET data types. I haven't done any code for that yet, but the idea is that you can eventuelly install one nuget package and add the node set file to your solution. Roslyn will then generate all the needed classes for you.

This would make it easy to provide classes for companion specifications and also for PLCs. With the Siemens TIA portal you can for instance export the NodeSet.xml file.

yang909044315 commented 1 year ago

I have UDT ,EX: {TYPE "Vector" VERSION : 0.1 STRUCT X : LReal; Y : LReal; Z : LReal; END_STRUCT;

END_TYPE};and SiemensPLC NodeId is "ns=2;i=3", How can I read and write?I use WPF ;Looking forward to your response

ismdiego commented 1 year ago

I'm playing at the moment with the idea, to write an source generator that will transform NodeSet2 files into .NET data types. I haven't done any code for that yet, but the idea is that you can eventuelly install one nuget package and add the node set file to your solution. Roslyn will then generate all the needed classes for you.

I have found your "playground" (https://github.com/quinmars/UaTypeGenerator) and it is brilliant!

The problem I actually have that prevents me from using it is that some vendors make it really hard or impossible to generate the .NodeSet2.xml file.

So I am trying to use your tool in a simpler way, passing as input something like the data that the OPC UA Browser plugin (for VS2019) generates when dragging a Structure type into a code window. In fact, it will be fine if this structure can be read from the OPC UA Server directly without using that plugin. I have to also check the plugin code to see how to achieve this (LoadChildrenAsync method from UaBrowserViewModel seems a good starting point).

@awcullen I suspect the https://github.com/convertersystems/opc-ua-tools is abandoned and you could not achieve the goal of generating the Decode and Encode code by itself. I think that combining the efforts in these two projects would help in that matter. @quinmars project is fantastic in generating the code, and the plugin can read the datatypes (at least enough to have all the required information to create the Decode/Encode methods).

I will try to use code from both to see if I can integrate them in a standalone utility.