QuickOPC User's Guide and Reference
Processing data types
Extensions > Integrated Extensions > OPC UA Complex Data Extension > Generic data and data types > Working with data types > Processing data types

In order to process a data type (a sub-type of DataType Class), you need to first determine its kind (see Data type kinds). You can do it by

There are also some common properties that all data types have, such as Name PropertyFullName Property and Description Property.

After you have determined the kind of data type you are dealing with, you can use the data type object cast to appropriate type, and work with properties and methods that are specific to that data type kind. For example, a SequenceDataType contains the length of the sequence (the Length Property), and the data type of its elements (the ElementDataType Property).

The following example shows how a data type can be processed.

.NET

// Shows how to process a data type, displaying some of its properties, recursively.

using System;
using System.Linq;
using OpcLabs.BaseLib.DataTypeModel;
using OpcLabs.EasyOpc.UA;
using OpcLabs.EasyOpc.UA.ComplexData;
using OpcLabs.EasyOpc.UA.OperationModel;

namespace UADocExamples.ComplexData._DataType
{
    class Kind
    {
        public static void Main1()
        {
            // Define which server and node we will work with.
            UAEndpointDescriptor endpointDescriptor =
                "opc.tcp://opcua.demo-this.com:51210/UA/SampleServer";
            // or "http://opcua.demo-this.com:51211/UA/SampleServer" (currently not supported)
            // or "https://opcua.demo-this.com:51212/UA/SampleServer/"
            UANodeDescriptor nodeDescriptor = 
                "nsu=http://test.org/UA/Data/ ;i=10239"; // [ObjectsFolder]/Data.Static.Scalar.StructureValue

            // Instantiate the client object.
            var client = new EasyUAClient();

            // Read a node. We know that this node returns complex data, so we can type cast to UAGenericObject.
            UAGenericObject genericObject;
            try
            {
                genericObject = (UAGenericObject)client.ReadValue(endpointDescriptor, nodeDescriptor);
            }
            catch (UAException uaException)
            {
                Console.WriteLine("*** Failure: {0}", uaException.GetBaseException().Message);
                return;
            }
            // The data type is in the GenericData.DataType property of the UAGenericObject.
            DataType dataType = genericObject.GenericData.DataType;

            // Process the data type. We will inspect some of its properties, and dump them.
            ProcessDataType(dataType, maximumDepth: 3);
        }
        

        // Process the data type. It can be recursive in itself, so if you do not know the data type you are dealing with, 
        // it is recommended to make safeguards against infinite looping or recursion - here, the maximumDepth.
        public static void ProcessDataType(DataType dataType, int maximumDepth)
        {
            if (maximumDepth == 0)
                return;

            Console.WriteLine();
            Console.WriteLine("dataType.Name: {0}", dataType.Name);

            switch (dataType.Kind)
            {
                case DataTypeKind.Enumeration:
                    Console.WriteLine("The data type is an enumeration.");
                    var enumerationDataType = (EnumerationDataType) dataType;
                    Console.WriteLine("It has {0} enumeration members.", enumerationDataType.EnumerationMembers.Count);
                    Console.WriteLine("The names of the enumeration members are: {0}.",
                        String.Join(", ", enumerationDataType.EnumerationMembers.Select(member => member.Name)));
                    // Here you can process the members, or inspect SizeInBits etc.
                    break;

                case DataTypeKind.Opaque:
                    Console.WriteLine("The data type is opaque.");
                    var opaqueDataType = (OpaqueDataType) dataType;
                    Console.WriteLine("Its size is {0} bits.", opaqueDataType.SizeInBits);
                    // There isn't much more you can learn about an opaque data type (well, it may have Description and 
                    // other common members). It is, after all, opaque...
                    break;

                case DataTypeKind.Primitive:
                    Console.WriteLine("The data type is primitive.");
                    var primitiveDataType = (PrimitiveDataType) dataType;
                    Console.WriteLine("Its .NET value type is \"{0}\".", primitiveDataType.ValueType);
                    // There isn't much more you can learn about the primitive data type.
                    break;

                case DataTypeKind.Sequence:
                    Console.WriteLine("The data type is a sequence.");
                    var sequenceDataType = (SequenceDataType) dataType;
                    Console.WriteLine("Its length is {0} (-1 means that the length can vary).", sequenceDataType.Length);

                    Console.WriteLine("A dump of the element data type follows.");
                    ProcessDataType(sequenceDataType.ElementDataType, maximumDepth - 1);
                    break;

                case DataTypeKind.Structured:
                    Console.WriteLine("The data type is structured.");
                    var structuredDataType = (StructuredDataType) dataType;
                    Console.WriteLine("It has {0} data fields.", structuredDataType.DataFields.Count);
                    Console.WriteLine("The names of the data fields are: {0}.",
                        String.Join(", ", structuredDataType.DataFields.Select(field => field.Name)));

                    Console.WriteLine("A dump of each of the data fields follows.");
                    foreach (DataField dataField in structuredDataType.DataFields)
                    {
                        Console.WriteLine();
                        Console.WriteLine("dataField.Name: {0}", dataField.Name);
                        // Note that every data field also has properties like IsLength, IsOptional, IsSwitch which might 
                        // be of interest, but we are not dumping them here.
                        ProcessDataType(dataField.DataType, maximumDepth - 1);
                    }
                    break;

                case DataTypeKind.Union:
                    Console.WriteLine("The data type is union.");
                    var unionDataType = (UnionDataType)dataType;
                    Console.WriteLine("It has {0} data fields.", unionDataType.DataFields.Count);
                    Console.WriteLine("The names of the data fields are: {0}.",
                        String.Join(", ", unionDataType.DataFields.Select(field => field.Name)));
                    break;
            }
        }
    }
}
# Shows how to process a data type, displaying some of its properties, recursively.

# The QuickOPC package is needed. Install it using "pip install opclabs_quickopc".
import opclabs_quickopc

# Import .NET namespaces.
from System import *
from OpcLabs.BaseLib.DataTypeModel import *
from OpcLabs.EasyOpc.UA import *
from OpcLabs.EasyOpc.UA.OperationModel import *


def processDataType(dataType, maximumDepth):
    if maximumDepth == 0:
        print('* Reached maximum depth *')
        return

    print()
    print('dataType.Name: ', dataType.Name, sep='')

    if dataType.Kind == DataTypeKind.Enumeration:
        print('The data type is an enumeration.')
        enumerationDataType = dataType
        print('It has ', enumerationDataType.EnumerationMembers.Count, ' enumeration members.', sep='')
        print('The names of the enumeration members are: ', end='')
        for i, member in enumerate(enumerationDataType.EnumerationMembers):
            if i != 0:
                print(', ', end='')
            print(member.Name, end='')
        print('.')
        # Here you can process the members, or inspect SizeInBits etc.

    elif dataType.Kind == DataTypeKind.Opaque:
        print('The data type is opaque.')
        opaqueDataType = dataType
        print('Its size is ', opaqueDataType.SizeInBits, ' bits.', sep='')
        # There isn't much more you can learn about an opaque data type (well, it may have Description and
        # other common members). It is, after all, opaque...

    elif dataType.Kind == DataTypeKind.Primitive:
        print('The data type is primitive.')
        primitiveDataType = dataType
        print('Its .NET value type is "', primitiveDataType.ValueType, '".', sep='')
        # There isn't much more you can learn about the primitive data type.

    elif dataType.Kind == DataTypeKind.Sequence:
        print('The data type is a sequence.')
        sequenceDataType = dataType
        print('Its length is ', sequenceDataType.Length, ' (-1 means that the length can vary).', sep='')

        print('A dump of the element data type follows.')
        processDataType(sequenceDataType.ElementDataType, maximumDepth - 1)

    elif dataType.Kind == DataTypeKind.Structured:
        print('The data type is structured.')
        structuredDataType = dataType
        print('It has ', structuredDataType.DataFields.Count, ' data fields.', sep='')
        print('The names of the data fields are: ', end='')
        for i, field in enumerate(structuredDataType.DataFields):
            if i != 0:
                print(', ', end='')
            print(field.Name, end='')
        print('.')

        print('A dump of each of the data fields follows.')
        for dataField in structuredDataType.DataFields:
            print()
            print('dataField.Name: ', dataField.Name, sep='')
            # Note that every data field also has properties like IsLength, IsOptional, IsSwitch which might
            # be of interest, but we are not dumping them here.
            processDataType(dataField.DataType, maximumDepth - 1)

    elif dataType.Kind == DataTypeKind.Union:
        print('The data type is union.')
        unionDataType = dataType
        print('It has ', unionDataType.DataFields.Count, ' data fields.', sep='')
        print('The names of the data fields are: ', end='')
        for i, field in enumerate(unionDataType.DataFields):
            if i != 0:
                print(', ', end='')
            print(field.Name, end='')
        print('.')


# Define which server and node we will work with.
endpointDescriptor = UAEndpointDescriptor('opc.tcp://opcua.demo-this.com:51210/UA/SampleServer')
# or 'http://opcua.demo-this.com:51211/UA/SampleServer' (currently not supported)
# or 'https://opcua.demo-this.com:51212/UA/SampleServer/'

# [ObjectsFolder]/Data.Static.Scalar.StructureValue
nodeDescriptor = UANodeDescriptor('nsu=http://test.org/UA/Data/ ;i=10239')

# Instantiate the client object.
client = EasyUAClient()

# Read a node. We know that this node returns complex data, so we can type cast to UAGenericObject.
try:
    print('Reading...')
    genericObject = IEasyUAClientExtension.ReadValue(client, endpointDescriptor, nodeDescriptor)
except UAException as uaException:
    print('*** Failure: ' + uaException.GetBaseException().Message)
    exit()
print('Reading successful.')
# The data type is in the GenericData.DataType property of the UAGenericObject.
dataType = genericObject.GenericData.DataType

# Process the data type. We will inspect some of its properties, and dump them.
processDataType(dataType, 3)

print()
print('Finished.')
' Shows how to process a data type, displaying some of its properties, recursively.

Imports System
Imports System.Linq
Imports OpcLabs.BaseLib.DataTypeModel
Imports OpcLabs.EasyOpc.UA
Imports OpcLabs.EasyOpc.UA.ComplexData
Imports OpcLabs.EasyOpc.UA.OperationModel

Namespace ComplexData._DataType

    Friend Class Kind

        Public Shared Sub Main1()

            ' Define which server we will work with.
            Dim endpointDescriptor As UAEndpointDescriptor =
                    "opc.tcp://opcua.demo-this.com:51210/UA/SampleServer"
            ' or "http://opcua.demo-this.com:51211/UA/SampleServer" (currently not supported)
            ' or "https://opcua.demo-this.com:51212/UA/SampleServer/"

            ' Define which node we will work with.
            Dim nodeDescriptor As UANodeDescriptor = _
                "nsu=http://test.org/UA/Data/ ;i=10239"  ' [ObjectsFolder]/Data.Static.Scalar.StructureValue

            ' Instantiate the client object.
            Dim client = New EasyUAClient

            ' Read a node. We know that this node returns complex data, so we can type cast to UAGenericObject.
            Dim genericObject As UAGenericObject
            Try
                genericObject = CType(client.ReadValue(endpointDescriptor, nodeDescriptor), UAGenericObject)
            Catch uaException As UAException
                Console.WriteLine("*** Failure: {0}", uaException.GetBaseException.Message)
                Exit Sub
            End Try
            ' The data type is in the GenericData.DataType property of the UAGenericObject.

            Dim dataType As DataType = genericObject.GenericData.DataType

            ' Process the data type. We will inspect some of its properties, and dump them.
            ProcessDataType(dataType, maximumDepth:=2)
        End Sub


        ' Process the data type. It can be recursive in itself, so if you do not know the data type you are dealing with, 
        ' it is recommended to make safeguards against infinite looping or recursion - here, the maximumDepth.
        Public Shared Sub ProcessDataType(dataType As DataType, ByVal maximumDepth As Integer)
            If (maximumDepth = 0) Then
                Return
            End If

            Console.WriteLine()
            Console.WriteLine("dataType.Name: {0}", dataType.Name)

            Select Case (dataType.Kind)
                Case DataTypeKind.Enumeration
                    Console.WriteLine("The data type is an enumeration.")
                    Dim enumerationDataType = CType(dataType, EnumerationDataType)
                    Console.WriteLine("It has {0} enumeration members.", enumerationDataType.EnumerationMembers.Count)
                    Console.WriteLine("The names of the enumeration members are: {0}.", _
                                      String.Join(", ", enumerationDataType.EnumerationMembers.Select(Function(member) member.Name)))
                    ' Here you can process the members, or inspect SizeInBits etc.

                Case DataTypeKind.Opaque
                    Console.WriteLine("The data type is opaque.")
                    Dim opaqueDataType = CType(dataType, OpaqueDataType)
                    Console.WriteLine("Its size is {0} bits.", opaqueDataType.SizeInBits)
                    ' There isn't much more you can learn about an opaque data type (well, it may have Description and 
                    ' other common members). It is, after all, opaque...

                Case DataTypeKind.Primitive
                    Console.WriteLine("The data type is primitive.")
                    Dim primitiveDataType = CType(dataType, PrimitiveDataType)
                    Console.WriteLine("Its .NET value type is ""{0}"".", primitiveDataType.ValueType)
                    ' There isn't much more you can learn about the primitive data type.

                Case DataTypeKind.Sequence
                    Console.WriteLine("The data type is a sequence.")
                    Dim sequenceDataType = CType(dataType, SequenceDataType)
                    Console.WriteLine("Its length is {0} (-1 means that the length can vary).", sequenceDataType.Length)
                    Console.WriteLine("A dump of the element data type follows.")
                    ProcessDataType(sequenceDataType.ElementDataType, (maximumDepth - 1))

                Case DataTypeKind.Structured
                    Console.WriteLine("The data type is structured.")
                    Dim structuredDataType = CType(dataType, StructuredDataType)
                    Console.WriteLine("It has {0} data fields.", structuredDataType.DataFields.Count)
                    Console.WriteLine("The names of the data fields are: {0}.", _
                                      String.Join(", ", structuredDataType.DataFields.Select(Function(field) field.Name)))
                    Console.WriteLine("A dump of each of the data fields follows.")

                    For Each dataField As DataField In structuredDataType.DataFields
                        Console.WriteLine()
                        Console.WriteLine("dataField.Name: {0}", dataField.Name)
                        ' Note that every data field also has properties like IsLength, IsOptional, IsSwitch which might 
                        ' be of interest but we are not dumping them here.
                        ProcessDataType(dataField.DataType, (maximumDepth - 1))
                    Next

                Case DataTypeKind.Union
                    Console.WriteLine("The data type is union.")
                    Dim unionDataType = CType(dataType, UnionDataType)
                    Console.WriteLine("It has {0} data fields.", unionDataType.DataFields.Count)
                    Console.WriteLine("The names of the data fields are: {0}.",
                                      String.Join(", ", unionDataType.DataFields.Select(Function(field) field.Name)))

            End Select

        End Sub
    End Class
End Namespace

COM

// Shows how to process a data type, displaying some of its properties, recursively.

class procedure Kind.Main;
var
  Client: _EasyUAClient;
  DataType: OpcLabs_BaseLib_TLB._DataType;
  EndpointDescriptor: string;
  GenericObject: _UAGenericObject;
  NodeDescriptor: string;
begin
  // Define which server and node we will work with.
  EndpointDescriptor := 
    //'http://opcua.demo-this.com:51211/UA/SampleServer';
    //'https://opcua.demo-this.com:51212/UA/SampleServer/';
    'opc.tcp://opcua.demo-this.com:51210/UA/SampleServer';
  NodeDescriptor := 'nsu=http://test.org/UA/Data/ ;i=10239';  // [ObjectsFolder]/Data.Static.Scalar.StructureValue

  // Instantiate the client object
  Client := CoEasyUAClient.Create;

  // Read a node. We know that this node returns complex data, so we can type cast to UAGenericObject.

  try
    GenericObject := IUnknown(Client.ReadValue(EndpointDescriptor, NodeDescriptor)) as _UAGenericObject;
  except
    on E: EOleException do
    begin
      WriteLn(Format('*** Failure: %s', [E.GetBaseException.Message]));
      Exit;
    end;
  end;

  // The data type is in the GenericData.DataType property of the UAGenericObject.
  DataType := genericObject.GenericData.DataType;

  // Process the data type. We will inspect some of its properties, and dump them.
  ProcessDataType(DataType, 2);
end;

// Process the data type. It can be recursive in itself, so if you do not know the data type you are dealing with,
// it is recommended to make safeguards against infinite looping or recursion - here, the maximumDepth.
class procedure Kind.ProcessDataType(DataType: OpcLabs_BaseLib_TLB._DataType; MaximumDepth: Cardinal);
var
  Count: Cardinal;
  DataField: _DataField;
  Element: OleVariant;
  ElementEnumerator: IEnumVARIANT;
  EnumerationMember: _EnumerationMember;
  EnumerationDataType: _EnumerationDataType;
  FieldNames: string;
  First: boolean;
  MemberNames: string;
  OpaqueDataType: _OpaqueDataType;
  PrimitiveDataType: _PrimitiveDataType;
  SequenceDataType: _SequenceDataType;
  StructuredDataType: _StructuredDataType;
  TypeName: WideString;
begin
  if MaximumDepth = 0 then
    Exit;

  WriteLn;
  WriteLn('dataType.Name: ', DataType.Name);

  case DataType.Kind of
    DataTypeKind_Enumeration:
      begin
        WriteLn('The data type is an enumeration.');
        EnumerationDataType := DataType as _EnumerationDataType;
        WriteLn(Format('It has %s enumeration members.', [EnumerationDataType.EnumerationMembers.Count]));
        ElementEnumerator := EnumerationDataType.EnumerationMembers.GetEnumerator;
        MemberNames := '';
        First := True;
        while (ElementEnumerator.Next(1, Element, Count) = S_OK) do
        begin
          EnumerationMember := IUnknown(Element) as _EnumerationMember;
          if First then
            First := False
          else
            MemberNames := MemberNames + ', ';
          MemberNames := MemberNames + EnumerationMember.Name;
        end;
        WriteLn(Format('The names of the enumeration members are: %s.', [MemberNames]));
        // Here you can process the members, or inspect SizeInBits etc.
      end;
    DataTypeKind_Opaque:
      begin
        WriteLn('The data type is opaque.');
        OpaqueDataType := DataType as _OpaqueDataType;
        WriteLn(Format('Its size is %s bits.', [OpaqueDataType.SizeInBits]));
        // There isn't much more you can learn about an opaque data type (well, it may have Description and
        // other common members). It is, after all, opaque...
      end;
    DataTypeKind_Primitive:
      begin
        WriteLn('The data type is primitive.');
        PrimitiveDataType := DataType as _PrimitiveDataType;
        PrimitiveDataType.ValueType.Get_ToString(TypeName);
        WriteLn(Format('Its .NET value type is "%s".', [TypeName]));
        // There isn't much more you can learn about the primitive data type.
      end;
    DataTypeKind_Sequence:
      begin
        WriteLn('The data type is a sequence.');
        SequenceDataType := DataType as _SequenceDataType;
        WriteLn(Format('Its length is %s (-1 means that the length can vary).', [SequenceDataType.Length.ToString]));
        WriteLn('A dump of the element data type follows.');
        ProcessDataType(SequenceDataType.ElementDataType, MaximumDepth - 1);
      end;
    DataTypeKind_Structured:
      begin
        WriteLn('The data type is structured.');
        StructuredDataType := DataType as _StructuredDataType;
        WriteLn(Format('It has %s data fields.', [StructuredDataType.DataFields.Count.ToString]));
        ElementEnumerator := StructuredDataType.DataFields.GetEnumerator;
        FieldNames := '';
        First := True;
        while (ElementEnumerator.Next(1, Element, Count) = S_OK) do
        begin
          if First then
            First := False
          else
            FieldNames := FieldNames + ', ';
          FieldNames := FieldNames + Element.Name;
        end;
        WriteLn(Format('The names of the data fields are: %s.', [FieldNames]));

        WriteLn('A dump of each of the data fields follows.');
        ElementEnumerator := StructuredDataType.DataFields.GetEnumerator;
        while (ElementEnumerator.Next(1, Element, Count) = S_OK) do
        begin
          DataField := IUnknown(Element) as _DataField;
          WriteLn;
          WriteLn(Format('dataField.Name: %s', [DataField.Name]));
          // Note that every data field also has properties like IsLength, IsOptional, IsSwitch which might
          // be of interest but we are not dumping them here.
          ProcessDataType(DataField.DataType, MaximumDepth - 1);
        end;
    end;
  end;

end;

 

In many cases, you will not be processing the data type alone, but in parallel with the generic data. See Processing generic data for details. 

See Also

Reference