RoddenLab / HMI-Demo

A small HMI and API demo project.
MIT License
0 stars 1 forks source link

Struct reading as ByteString instead of Encodable #1

Closed rmnbs closed 1 year ago

rmnbs commented 1 year ago

Hi,

Like you I'm trying to create an MVVM application for a beckhoff automation project.

I'm trying to read my Status structure.

[DataTypeId("ns=4;s=<StructuredDataType>:Status")]
[BinaryEncodingId("ns=4;s=<StructuredDataType>:Status__DefaultBinary")]
public class Status : Structure
{
/*
<opc:StructuredType Name="Status" BaseType="ua:ExtensionObject" xmlns:opc="http://opcfoundation.org/BinarySchema/">
  <opc:Field Name="Init" TypeName="opc:Boolean" />
  <opc:Field Name="Ready" TypeName="opc:Boolean" />
  <opc:Field Name="Busy" TypeName="opc:Boolean" />
  <opc:Field Name="EMR_Safe" TypeName="opc:Boolean" />
  <opc:Field Name="SPACER_Safe" TypeName="opc:Boolean" />
  <opc:Field Name="Error" TypeName="opc:Boolean" />
  <opc:Field Name="ErrorCode" TypeName="opc:UInt32" />
  <opc:Field Name="AtStation" TypeName="tns:Stations" />
  <opc:Field Name="ProcessState" TypeName="tns:EMRStates" />
  <opc:Field Name="GUIControl" TypeName="opc:Boolean" />
  <opc:Field Name="AllLocked" TypeName="opc:Boolean" />
</opc:StructuredType>
*/

    public bool Init { get; set; } = false;
    public bool Ready{ get; set; } = false;
    public bool Busy { get; set; } = false;
    public bool EMR_Safe { get; set; } = false;
    public Boolean SPACER_Safe { get; set; } = false;
    public Boolean Error { get; set; } = false;
    public UInt32 ErrorCode { get; set; } = 0;
    public Stations AtStation { get; set; } = Stations.NoStation;
    public EMRStates ProcessState { get; set; } = EMRStates.NotReady;
    public Boolean GUIControl { get; set; } = false;
    public Boolean AllLocked { get; set; } = false;

    public override void Encode(IEncoder encoder)
    {
        encoder.PushNamespace("urn:BeckhoffAutomation:Ua:PLC1");
        encoder.WriteBoolean("Init", Init);
        encoder.WriteBoolean("Ready", Ready);
        encoder.WriteBoolean("Busy", Busy);
        encoder.WriteBoolean("EMR_Safe", EMR_Safe);
        encoder.WriteBoolean("SPACER_Safe", SPACER_Safe);
        encoder.WriteBoolean("Error", Error);
        encoder.WriteUInt32("ErrorCode", ErrorCode);
        encoder.WriteEnumeration("AtStation", AtStation);
        encoder.WriteEnumeration("ProcessState", ProcessState);
        encoder.WriteBoolean("GUIControl", GUIControl);
        encoder.WriteBoolean("AllLocked", AllLocked);
        encoder.PopNamespace();
    }

    public override void Decode(IDecoder decoder)
    {
        decoder.PushNamespace("urn:BeckhoffAutomation:Ua:PLC1");
        Init = decoder.ReadBoolean("Init");
        Ready = decoder.ReadBoolean("Ready");
        Busy = decoder.ReadBoolean("Busy");
        EMR_Safe = decoder.ReadBoolean("EMR_Safe");
        SPACER_Safe = decoder.ReadBoolean("SPACER_Safe");
        Error = decoder.ReadBoolean("Error");
        ErrorCode = decoder.ReadUInt32("ErrorCode");
        AtStation = decoder.ReadEnumeration<Stations>("AtStation");
        ProcessState = decoder.ReadEnumeration<EMRStates>("ProcessState");
        GUIControl = decoder.ReadBoolean("GUIControl");
        AllLocked = decoder.ReadBoolean("AllLocked");
        decoder.PopNamespace();
    }
}

I am unable read my struct with the MonitoredItem / Structure pattern.

To find out why I created a small Terminal app trying to read just that structure.

var clientDescription = new ApplicationDescription
{
    ApplicationName = "Workstation.UaClient.FeatureTests",
    ApplicationUri = $"urn:{System.Net.Dns.GetHostName()}:Workstation.UaClient.FeatureTests",
    ApplicationType = ApplicationType.Client
};

// create a 'ClientSessionChannel', a client-side channel that opens a 'session' with the server.
var channel = new ClientSessionChannel(
    clientDescription,
    null, // no x509 certificates
    new UserNameIdentity("", ""), // not the actual user identity
    "opc.tcp://localhost:4840", // not the actual endpoint.
    SecurityPolicyUris.None); // no encryption

await channel.OpenAsync();

// build a ReadRequest. See 'OPC UA Spec Part 4' paragraph 5.10.2
var readRequest = new ReadRequest {
    // set the NodesToRead to an array of ReadValueIds.
    NodesToRead = new[] {
        // construct a ReadValueId from a NodeId and AttributeId.
        new ReadValueId {
            // you can parse the nodeId from a string.
            // e.g. NodeId.Parse("ns=2;s=Demo.Static.Scalar.Double")
            NodeId = NodeId.Parse("ns=4;s=GVL.Status"),
            // variable class nodes have a Value attribute.
            AttributeId = AttributeIds.Value
        }
    }
};

var readResult = await channel.ReadAsync(readRequest);
var a = readResult.Results[0].Variant.Value as ExtensionObject; 
Console.WriteLine(a.BodyType);
var serverStatus = readResult.Results[0].GetValueOrDefault<CustomTypeLibrary.Status>();

What I get is that BodyType is of type ByteString. for GetValueOrDefault to function properly I would need an Encodable.

I don't understand why i'm reading the data as a ByteString. For context I have tried multiple ways to configure the structure. directly on the structure itself

{attribute 'OPC.UA.DA.StructuredType' := '1'}
{attribute 'OPC.UA.DA' := '2'}
TYPE Status :
STRUCT
    Init : BOOL;
    Ready : BOOL;
    Busy : BOOL;
    EMR_Safe : BOOL; (* Safe if EMR is not in RedZone *)
    SPACER_Safe : BOOL := FALSE; (* False on default so EMR only sets it to true when Spacer sends Event/Spacer_safe *)
    Error : BOOL;
    ErrorCode : UDINT; 
    AtStation : Stations;
    ProcessState : EMRStates;
    GUIControl : BOOL; // sets to True when GUI and control is assumed by GUI on this mode, TCPip commands are ignored basically teach mode
    AllLocked : BOOL;
END_STRUCT
END_TYPE

or on the variable

    {attribute 'OPC.UA.DA.StructuredType' := '1'}
    {attribute 'OPC.UA.DA' := '2'}
    Status : Status;

without success. I feel like it's a configuration issue somewhere on the client or server but I can't find it. If you have any idea about what is it I am doing wrong I'll gladly accept it. Thanks.

rmnbs commented 1 year ago

Solved it,

I Was doing [BinaryEncodingId("nsu=urn:BeckhoffAutomation:Ua:PLC1;ns=4;s=<StructuredDataType>:StationConfig__DefaultBinary")] or [BinaryEncodingId("ns=4;s=<StructuredDataType>:StationConfig__DefaultBinary")]

instead of [BinaryEncodingId("nsu=urn:BeckhoffAutomation:Ua:PLC1;s=<StructuredDataType>:StationConfig__DefaultBinary")]