Method call for custom complex types.|OPC UA Implementation: Stacks, Tools, and Samples|Forum|OPC Foundation

Avatar
Search
Forum Scope


Match



Forum Options



Minimum search word length is 3 characters - maximum search word length is 84 characters
Lost password?
sp_Feed sp_PrintTopic sp_TopicIcon
Method call for custom complex types.
Avatar
Shaun Hennessy
Member
Members
Forum Posts: 9
Member Since:
05/25/2021
sp_UserOfflineSmall Offline
1
01/25/2022 - 06:23
sp_Permalink sp_Print

Hi, I am new to OPC ua and I am having problems calling a server method with a custom type from an opc ua client.

The server and client are both in house applications using the opc ua foundation sdk. 

The client references Opc.Ua.Client, Opc.Ua.Client.ComplexTypes, Opc.Ua.Configuration, Opc.Ua.Core.

The custom type is of this structure:

st_outputConfiguration

     Distance  int

     Duration int

     Status   enum

The method takes the following parameters:

StrobeId  int,

OutputConfiguration  array of structure st_outputConfiguration

I can get the type st_outputConfiguration structure from the server and build an array however I get an error when calling the method:

"InvalidCastExecption - Object cannot be stored in an array of this type"

 

Here is the relevant code snippet:

NodeId node = (NodeId)objectReference.NodeId;   //Node id of the object containing the method
NodeId method = (NodeId)methodReference.NodeId; // Node id of the method.

Int16 strobeIndex = 1;                                            //hard coded test of the strobeIndex parameter
List<Object> inputArguments = new List<Object>();   // an object to contain the all the input arguments for the method.
inputArguments.Add(strobeIndex);

// Get the st_outputConfiguration type from the server
Opc.Ua.Client.ComplexTypes.ComplexTypeSystem cts = new Opc.Ua.Client.ComplexTypes.ComplexTypeSystem(m_s);
Task<Type>t = cts.LoadType(ExpandedNodeId.Parse("ns=6;i=100050"));   // id for the custom type.
NodeId nodeId = NodeId.Parse("ns=6;i=100050");

 

// build array of st_outputConfiguration

dynamic[] arOutputs = new dynamic[14];
Type typ = (Type)t.Result;                             
for (int i = 0; i < arOutputs.Length; i++)
{
dynamic st = Activator.CreateInstance(typ);
st.Distance = 1;
st.Duration = 1;
st.Status = 0;
arOutputs[i] = st;
}
inputArguments.Add(arOutputs);  // add to the input arguments

var result = m_s.Call(node, method, inputArguments);  // call the method.

End of Code Snippet.

I'm new to this and may be trying something that is difficult to implement, but I've seen examples that seem to work and calling the method from UA Expert works with no problem, so I know it's possible.

Can anyone help point me in the right direction. Thanks.

Avatar
Randy Armstrong
Admin
Forum Posts: 1438
Member Since:
05/30/2017
sp_UserOfflineSmall Offline
2
01/25/2022 - 15:49
sp_Permalink sp_Print

The C# API takes an Object but complex types must be wrapped in an ExtensionObject.

Any type that implements the IEncodeable interface can be placed in an ExtensionObject.

Avatar
Shaun Hennessy
Member
Members
Forum Posts: 9
Member Since:
05/25/2021
sp_UserOfflineSmall Offline
3
02/01/2022 - 07:02
sp_Permalink sp_Print

Randy Armstrong said
The C# API takes an Object but complex types must be wrapped in an ExtensionObject.

Any type that implements the IEncodeable interface can be placed in an ExtensionObject.

  

Thanks for your feedback. I've taken another attempt but I'm still having problems.

Here is my new code for making the method call:

NodeId node = (NodeId)objectReference.NodeId;
NodeId method = (NodeId)methodReference.NodeId;

EncodeableFactory.GlobalFactory.AddEncodeableType(typeof(ST_ClientTEST02));
ST_ClientTEST02 v1 = new ST_ClientTEST02();
v1.Distance = 1;
v1.Duration = 1;
v1.Status = 1;

var result = m_s.Call(node, method, new ExtensionObject(v1));

 

I am getting an invalid argument error.

 

This is the IEncodable class I've created: 

public class ST_ClientTEST02 : EncodeableObject
{
public UInt32 Distance { get; set; }
public UInt32 Duration { get; set; }
public UInt32 Status { get; set; }

public override ExpandedNodeId TypeId
{
get { return ExpandedNodeId.Parse("ns=6;i=100170"); }
//protected set { _myProp = value; }
}

public override ExpandedNodeId BinaryEncodingId
{
get { return ExpandedNodeId.Parse("ns=6;i=100175"); }
}

public override ExpandedNodeId XmlEncodingId
{
get { return null; }
}

public override void Encode(IEncoder encoder)
{
encoder.WriteUInt32("Distance", this.Distance);
encoder.WriteUInt32("Duration", this.Duration);
encoder.WriteUInt32("Status", this.Status);
}

public override void Decode(IDecoder decoder)
{
this.Distance = decoder.ReadUInt32("Distance");
this.Duration = decoder.ReadUInt32("Duration");
this.Status = decoder.ReadUInt32("Status");
}

public override string ToString()
{
return "{distance=" + this.Distance.ToString() + "; Duration=" + Duration.ToString() + "; status=" + this.Status.ToString() + "}";
}
}

 

Any pointers?

Avatar
Randy Armstrong
Admin
Forum Posts: 1438
Member Since:
05/30/2017
sp_UserOfflineSmall Offline
4
02/01/2022 - 09:10
sp_Permalink sp_Print

Your URIs - not indexes in the TypeId and BinaryEncodingId properties.

Avatar
Shaun Hennessy
Member
Members
Forum Posts: 9
Member Since:
05/25/2021
sp_UserOfflineSmall Offline
5
02/14/2022 - 07:46
sp_Permalink sp_Print

Thanks for you feedback. 

I've  been trying to get this working with the correct uri/nsu, but I can't get it working. I've only been using the index previously and am not sure I'm formatting the uri correctly. Is there a way of translating from index to nsu/uri to make sure I've got the correct uri/nsu? I can't seem to find a way of getting this.

Avatar
Randy Armstrong
Admin
Forum Posts: 1438
Member Since:
05/30/2017
sp_UserOfflineSmall Offline
6
02/15/2022 - 03:03
sp_Permalink sp_Print

When you create a Session the client library reads the value of the NamespaceArray Property and caches it.

When you pass a IEncodeable as a parameter the URI stored in the TypeId is replaced by the NamespaceIndex for the current NamespaceArray.

The URI is an opaque string. A simple case sensitive compare is done with each entry in the NamespaceArray. Omitting trailing slashes or similar typing errors can lead to a failure to locate the URI.

Avatar
Shaun Hennessy
Member
Members
Forum Posts: 9
Member Since:
05/25/2021
sp_UserOfflineSmall Offline
7
02/15/2022 - 04:51
sp_Permalink sp_Print

Thanks for replying Randy.

I'm aware of what the IEncodable passed in will do. Whatever I try I can't get the TypeId or BinaryEncoding Id correct.

I'm taking a step back and trying to verify I can get the URI address correct and read a node through the session.ReadNode 

As I say I'm a complete novice at this so may be making simple basic errors, but I hitting brick walls I can't find answers to so I'm completely stuck.

Code snippet of what I'm trying to do.

string id = "";

// i can read this node ok
id = "ns=6;i=100055";
var val1 = m_s.ReadNode(NodeId.Parse(id));

//Contents of server namespace array property
//[0] https://opcfoundation.org/UA/
//[1] urn:br-automation/BR/UA/EmbeddedServer
//[2] https://opcfoundation.org/UA/DI/
//[3] http://PLCopen.org/OpcUa/IEC61131-3/
//[4] http://br-automation.com/OpcUa/PLC/
//[5]
//[6] http://br-automation.com/OpcUa/PLC/PV/

// I'm trying to veryify a URI/NSU method of reading the node, then I can veryify I at least have the address correct.
// This does not work - Bad Id.
id = "http://br-automation.com/OpcUa/PLC/;i=100055";
NodeId nid = NodeId.Parse(id);    // I'm assuming this can take a URI? If not how can I read a node using the URI?
var val2 = m_s.ReadNode(nid);

Avatar
Randy Armstrong
Admin
Forum Posts: 1438
Member Since:
05/30/2017
sp_UserOfflineSmall Offline
8
02/15/2022 - 10:58
sp_Permalink sp_Print

If you are writing a custom struct to a server you can just pass the ExtensionObject.

If you are reading a custom struct you will get a ExtensionObject with a binary body that you need to manually parse using your Decode method.

If you register your custom type with the stack using the MessageContext.Factory.AddEncodeableType then the stack will decode automatically for you but I would get the manually decoding working first.

Avatar
Shaun Hennessy
Member
Members
Forum Posts: 9
Member Since:
05/25/2021
sp_UserOfflineSmall Offline
9
02/23/2022 - 09:04
sp_Permalink sp_Print

Thank you very much. I have it working now.

I found it tricky to know precisely how to use the Extension Object. I couldn't find any detailed documentation - even  a list of the properties and methods on the ExtensionObject class. Can anyone point me to any documentation?

I ended up either looking at the code for the ExtensionObject Class in GitHub or using wireshark to see how UA expert formulated the messages. 

Thanks for all the help on this thread. It's been a huge help to a newbie like me.

Avatar
Mike Küster
Germany
Member
Members
Forum Posts: 6
Member Since:
07/04/2022
sp_UserOfflineSmall Offline
10
07/14/2022 - 09:20
sp_Permalink sp_Print

Hello,

I read this thread and think that I have the same problem like Shaun: reading complex types from a B&R PLC. 😉

@Shaun: It would be very helpful, if you can explain a little bit more what you have to do to get it working.

 

Randy Armstrong said
If you are reading a custom struct you will get a ExtensionObject with a binary body that you need to manually parse using your Decode method.

If I tried to use "ReadValue" on such a complex type, I got always a "BadNotSupported" StatusCode. (Same with the free UaExpert app: NodeClass = Variable, DataType = Structure, Value = BadNotSupported.) I can see, that the node has the attribute "HasTypeDefinition = myType" and I can also find the type in the Address Space: Types => VariableTypes => BaseVariableType => BaseDataVariableType => myType

 

With the input from this thread, I found the "NetCoreComplexClient" sample:

var value = m_session.ReadValue(variableNode.NodeId);

CastInt32ToEnum(variableNode, value);
Console.WriteLine($" -- {variableNode}:{value}");

if (value.Value is ExtensionObject extensionObject)
{ ...

or this thread: https://opcfoundation.org/foru.....ionobject/

But for all of them, I need the DataValue. => BadNotSupported

What could be the reason for that? What I'm missing?

 

Thanks,
Mike

Avatar
Randy Armstrong
Admin
Forum Posts: 1438
Member Since:
05/30/2017
sp_UserOfflineSmall Offline
11
07/14/2022 - 10:59
sp_Permalink sp_Print sp_EditHistory

Are the the AccessLevel and UserAccessLevel?

Avatar
Mike Küster
Germany
Member
Members
Forum Posts: 6
Member Since:
07/04/2022
sp_UserOfflineSmall Offline
12
07/15/2022 - 00:38
sp_Permalink sp_Print

In the moment there is "no security": The "everyone" role has all rights.

 

(We need the communication only within a device, without access from outside.)

Avatar
Randy Armstrong
Admin
Forum Posts: 1438
Member Since:
05/30/2017
sp_UserOfflineSmall Offline
13
07/15/2022 - 05:31
sp_Permalink sp_Print

What are the values of the AccessLevel and UserAccessLevel?

What you think you have configured is not necessarily what you got.

Avatar
Mike Küster
Germany
Member
Members
Forum Posts: 6
Member Since:
07/04/2022
sp_UserOfflineSmall Offline
14
07/15/2022 - 06:06
sp_Permalink sp_Print

All nodes (basic or complex types) have the same values:

  • AccessLevel: CurrentRead, CurrentWrite
  • UserAccessLevel: CurrentRead, CurrentWrite
Avatar
Randy Armstrong
Admin
Forum Posts: 1438
Member Since:
05/30/2017
sp_UserOfflineSmall Offline
15
07/16/2022 - 20:30
sp_Permalink sp_Print

Sounds like a Server error.

BadNotSupported on a Read suggests that you are asking the Server to return something it can't provide.

Since UAExpert sees the same error is it not likely a issue with parameters.

At this point it seems to be a server bug. You need to contact B&R and ask them to explain why their Server would return BadNotSupported.

Avatar
Mike Küster
Germany
Member
Members
Forum Posts: 6
Member Since:
07/04/2022
sp_UserOfflineSmall Offline
16
07/26/2022 - 09:37
sp_Permalink sp_Print

Hi,

I got the answer from the B&R support:

I have to set the version of the "Informationmodel" to 2.0. (In the B&R IDE "Automation Studio" go to Physical View : CPU : Configuration : OPC-UA System : Informationmodels : PV : Version => 2.0.)

And my client has to load the ComplexTypeSystem:

var complexTypeSystem = new ComplexTypeSystem(Session);
await complexTypeSystem.Load().ConfigureAwait(false);

 

After that and with a little bit of .NET Reflection, I can now read complex types from the OPC UA server. 🙂

Thanks,

Mike

Forum Timezone: America/Phoenix
Most Users Ever Online: 510
Currently Online:
Guest(s) 27
Currently Browsing this Page:
1 Guest(s)
Top Posters:
Forum Stats:
Groups: 2
Forums: 10
Topics: 1341
Posts: 4545