OPCFoundation / UA-.NETStandard

OPC Unified Architecture .NET Standard
Other
1.96k stars 946 forks source link

Writing value read from server back yields BadTypeMismatch #2197

Open Neil336 opened 1 year ago

Neil336 commented 1 year ago

Type of issue

Current Behavior

To summarize: I can't write a custom datatype to the server, because I get a BadTypeMismatch.

In more detail: I tried to break the structure of the custom datatype down to a minimum and then read the value on the server and write it back directly. Using console output the value and datatype of the read and the written value are the exact same, but I get a BadTypeMismatch. Below I have listed some other approaches I have tried (I'm sorry for the huge text, but I hope I didn't miss anything important):

Additional info: Reading works fine, I can see the results in the Softing OPC UA Client or in the terminal (here as XML Element). The custom datatype variable has Access Level and User Access Level "CurrentRead, CurrentWrite" (3).

Code: In the following code I changed the TypeName, FieldName and NodeIds to symbolic names.

From the Program.cs code:

ReadValueIdCollection nodesToRead = new ReadValueIdCollection()
{
    new ReadValueId() { NodeId = "ns=idx;s=path.to.variable", AttributeId = Attributes.Value },
    new ReadValueId() { NodeId = "ns=idx;s=path.to.variable", AttributeId = Attributes.DataType },
};
session.Read(
    null,
    0,
    TimestampsToReturn.Both,
    nodesToRead,
    out DataValueCollection resultsValues,
    out DiagnosticInfoCollection diagnosticInfos);
WriteValue nodeToWrite = new WriteValue()
{
    NodeId = "ns=idx;s=path.to.variable",
    AttributeId = Attributes.Value,
};
nodeToWrite.Value = resultsValues[0]; 
//setting timestamps to default otherwise BadWriteNotSupported
nodeToWrite.Value.SourceTimestamp = default(DateTime);
nodeToWrite.Value.ServerTimestamp = default(DateTime);
WriteValueCollection nodesToWrite = new WriteValueCollection()
{
  nodeToWrite
};
session.Write(
  null,
  nodesToWrite,
  out StatusCodeCollection writeResultsValues,
  out DiagnosticInfoCollection writeDiagnosticInfos
  );

Additionally, here are the Variable and Type definition:

<UAVariable DataType="ns=idx2;s=path.to.datatype" NodeId="ns=idx;s=path.to.variable" BrowseName="idx2:TypeName" ParentNodeId="ns=idx;s=path.to.parent" UserAccessLevel="3" AccessLevel="3">
    <DisplayName>TypeName</DisplayName>
    <References>
<Reference ReferenceType="HasComponent" IsForward="false">ns=idx;s=path.to.parent</Reference>
        <Reference ReferenceType="HasTypeDefinition">i=63</Reference>
    </References>
     <Value>
        <uax:ExtensionObject>
            <uax:TypeId>
                <uax:Identifier>ns=idx2;s=path.to.datatype</uax:Identifier>
            </uax:TypeId>
            <uax:Body>
                <TypeName xmlns="http://opcfoundation.org/UA/2008/02/Types.xsd">
                    <FieldName>6</FieldName>
                </TypeName>
            </uax:Body>
        </uax:ExtensionObject>
    </Value>
</UAVariable>
<UADataType NodeId="ns=idx2;s=path.to.datatype" BrowseName="idx2:TypeName">
    <DisplayName>TypeName</DisplayName>
    <References>
        <Reference ReferenceType="HasSubtype" IsForward="false">i=22</Reference>
        <Reference ReferenceType="HasEncoding">ns=idx2;i=someIdx</Reference>
        <Reference ReferenceType="HasEncoding">ns=idx2;i=someOtherIdx</Reference>
    </References>
    <Definition Name="idx2:TypeName">
        <Field DataType="Int16" Name="FieldName"/>
    </Definition>
</UADataType>
<UAObject SymbolicName="DefaultBinary" NodeId="ns=idx2;i=someIdx" BrowseName="Default Binary">
    <DisplayName>Default Binary</DisplayName>
    <References>
        <Reference ReferenceType="HasTypeDefinition">i=76</Reference>
    </References>
</UAObject>
<UAObject SymbolicName="DefaultXML" NodeId="ns=idx2;i=someOtherIdx" BrowseName="Default XML">
    <DisplayName>Default XML</DisplayName>
    <References>
        <Reference ReferenceType="HasTypeDefinition">i=76</Reference>
    </References>
</UAObject>

Thank you for your help :)

Expected Behavior

The behavior I expect is to be able to write to a custom datatype on the server, or at least to it's fields. In the end I would have more fields including nested structures.

Steps To Reproduce

No response

Environment

- OS: Windows 11
- Environment: Visual Studio 2022 17.6.2
- Runtime:.NET 4.8.04161
- Nuget Version: 6.6.0
- Component: Opc.Ua
- Server: Custom as in https://github.com/OPCFoundation/UA-.NETStandard/blob/master/Applications/ConsoleReferenceServer/UAServer.cs
- Client: Custom as in https://github.com/OPCFoundation/UA-.NETStandard/blob/master/Applications/ConsoleReferenceClient/UAClient.cs

Anything else?

I also tried various different approaches:

1) creating a class in C# which extends EncodableObject with the field and the following functions:

public override ExpandedNodeId TypeId => ExpandedNodeId.Parse("nsu=expandedIdx;s=path.to.datatype");

public override ExpandedNodeId BinaryEncodingId => ExpandedNodeId.Parse("nsu=expandedIdx;i=someIdx");

public override ExpandedNodeId XmlEncodingId => ExpandedNodeId.Parse("nsu=expandedIdx;i=someOtherIdx");

public override void Encode(IEncoder encoder)
{
    encoder.WriteInt16("FieldName", this.FieldName);
}
public override void Decode(IDecoder decoder)
{
    this.FieldName= decoder.ReadInt16("FieldName");
}

a) here I then first tried something similar to https://github.com/OPCFoundation/UA-.NETStandard/issues/790 (the constructor creates new instance with FieldName initializes to 0):

EncodeableFactory.GlobalFactory.AddEncodeableType(typeof(TypeName));
var val = new TypeName();
WriteValue nodeToWrite = new WriteValue()
{
    NodeId = "ns=idx;s=path.to.variable",
    AttributeId = Attributes.Value,
    Value = new DataValue(new Variant(new ExtensionObject(val)))
};

b) Then I thought I might need to make use of the Encoding, thus I changed it to:

EncodeableFactory.GlobalFactory.AddEncodeableType(typeof(TypeName));
var val = new TypeName();
WriteValue nodeToWrite = new WriteValue()
{
    NodeId = "ns=idx;s=path.to.variable",
    AttributeId = Attributes.Value,
    Value = new DataValue(EncodeXml(val))
};

c) I also tried to manually recreating the value:

WriteValue nodeToWrite = new WriteValue()
{
    NodeId = "ns=idx;s=path.to.variable",
    AttributeId = Attributes.Value,
};
ExpandedNodeId typeId = ExpandedNodeId.Parse("nsu=expandedIdx;s=path.to.datatype");*/
XmlDocument xmlDocument = new XmlDocument();
xmlDocument.LoadXml("<TypeName xmlns = \"http://opcfoundation.org/UA/2008/02/Types.xsd\"><FieldName>6</FieldName></TypeName>");
XmlElement body = xmlDocument.DocumentElement;
ExpandedNodeId typeId = ExpandedNodeId.Parse("nsu=expandedIdx;s=path.to.datatype");
nodeToWrite.Value = new DataValue(new ExtensionObject(typeId, body));

2) In a different approach I tried to change the XML file to include HasComponent in the hopes that I can change the components (which then are regular types). However, in the end I was able to adjust the components (the fields), but the change was not reflected in the value of the parent. The XML now looks like this:

<UAVariable DataType="ns=idx2;s=path.to.datatype" NodeId="ns=idx;s=path.to.variable" BrowseName="idx2:TypeName" ParentNodeId="ns=idx;s=path.to.parent" UserAccessLevel="3" AccessLevel="3">
    <DisplayName>TypeName</DisplayName>
    <References>
    <Reference ReferenceType="HasComponent">ns=idx2;i=someIdx1</Reference>
    <Reference ReferenceType="HasComponent" IsForward="false">ns=idx;s=path.to.parent</Reference>
        <Reference ReferenceType="HasTypeDefinition">ns=idx2;i=someIdx2</Reference>
    </References>
</UAVariable>
<UAVariableType NodeId="ns=idx2;i=someIdx2" BrowseName="DataTypeName" DataType="ns=idx2;s=path.to.datatype">
    <DisplayName>DataTypeName</DisplayName>
    <References>
        <Reference ReferenceType="HasComponent">ns=idx2;i=someIdx3</Reference>
    <Reference ReferenceType="HasSubtype" IsForward="false">i=63</Reference>
    </References>
</UAVariableType>
<UAVariable NodeId="ns=idx2;i=someIdx3" BrowseName="FieldName" ParentNodeId="ns=idx2;i=someIdx2" DataType="Int16">
   <DisplayName>FieldName</DisplayName>
   <References>
    <Reference ReferenceType="HasTypeDefinition">i=63</Reference>
    <Reference ReferenceType="HasModellingRule">i=78</Reference>
    <Reference ReferenceType="HasComponent" IsForward="false">ns=idx2;i=someIdx2</Reference>
    </References>
    <Value>
    <Int16 xmlns="http://opcfoundation.org/UA/2008/02/Types.xsd">6</Int16>
    </Value>
</UAVariable>
<UAVariable NodeId="ns=idx2;i=someIdx1" BrowseName="FieldName" ParentNodeId="ns=idx;s=path.to.variable" DataType="Int16" UserAccessLevel="3" AccessLevel="3">
    <DisplayName>FieldName</DisplayName>
    <References>
    <Reference ReferenceType="HasTypeDefinition">i=63</Reference>
    <Reference ReferenceType="HasComponent" IsForward="false">ns=idx;s=path.to.variable</Reference>
    </References>
    <Value>
    <Int16 xmlns="http://opcfoundation.org/UA/2008/02/Types.xsd">4</Int16>
    </Value>
</UAVariable>
<UADataType NodeId="ns=idx2;s=path.to.datatype" BrowseName="idx2:TypeName">
    <DisplayName>TypeName</DisplayName>
    <References>
        <Reference ReferenceType="HasSubtype" IsForward="false">i=22</Reference>
        <Reference ReferenceType="HasEncoding">ns=idx2;i=someIdx</Reference>
        <Reference ReferenceType="HasEncoding">ns=idx2;i=someOtherIdx</Reference>
    </References>
    <Definition Name="idx2:TypeName">
        <Field DataType="Int16" Name="FieldName"/>
    </Definition>
</UADataType>
<UAObject SymbolicName="DefaultBinary" NodeId="ns=idx2;i=someIdx" BrowseName="Default Binary">
    <DisplayName>Default Binary</DisplayName>
    <References>
        <Reference ReferenceType="HasTypeDefinition">i=76</Reference>
    </References>
</UAObject>
<UAObject SymbolicName="DefaultXML" NodeId="ns=idx2;i=someOtherIdx" BrowseName="Default XML"> 
    <DisplayName>Default XML</DisplayName>
    <References>
        <Reference ReferenceType="HasTypeDefinition">i=76</Reference>
    </References>
</UAObject>

3) using the following functions yield no types:

await LoadTypeSystem(session).ConfigureAwait(false); //before reading/writing
Console.WriteLine(FetchAllNodesNodeCache(uaClient, "ns=idx2;s=path.to.variable")); //after reading/writing
LoadTypeSystem(session); //after reading/writing
Neil336 commented 1 year ago

I was able to get it to write to the server. From the Program.cs I can also get the value back and decode the bytes to the type. Both write and read have StatusCode Good. However, when using Softing OPC UA Client, I get "Message: Exception - Unexpected error processing response.: PUBLISH # 11 - Unhandled error during Publish. BadUnknownResponse 'Unexpected error processing response.'" and "Exception - Unable to read beyond the end of the stream.: Unexpected error processing response. EndOfStreamException 'Unable to read beyond the end of the stream.'" When using UaExpert I can see a long 0x number under Value->Value->Data, but Value->Value is labeled as UnknownExtensionObject. When double clicking on Value->Value->Data it shows "UaGenericStructureValue::setGenericValue returned BadTypeMismatch"