FreeOpcUa / python-opcua

LGPL Pure Python OPC-UA Client and Server
http://freeopcua.github.io/
GNU Lesser General Public License v3.0
1.36k stars 658 forks source link

How to use custom structure as InputArgument in method? #1154

Open AndreyKl4 opened 4 years ago

AndreyKl4 commented 4 years ago

Hi all!

i am a very new with OPC UA. Perhaps, my question is a stupid one but for me, it is really hard nut to crack=) I have defined my own structure as in example(https://github.com/FreeOpcUa/python-opcua/blob/master/examples/server-create-custom-structures.py) and after that i tried to set my custom structure as an InputArgument in my method:

   basic_var = server.nodes.objects.add_variable(ua.NodeId(namespaceidx=nodeid), 'BasicStruct',
                                                            ua.Variant(None, ua.VariantType.Null),
                                                            datatype=basic_struct.data_type)
    basic_var = ua.Argument()
    basic_var.Name = 'Test_structure'
    basic_var.DataType = basic_struct.data_type
    basic_var.ValueRank = -3 
    basic_var.ArrayDimensions = 0

    node_methods.add_method(nodeid, 'simple_callback', call_back ,[basic_var], [])  

In UaExpert Client I can monitor my structure as InputArgument of my method but when i click ''...'' i see empty ''value'' and '' name'' fields. Perhaps smb have already faced such problem and can help me with this issue=)

Thanks in advance!!!

Andrey

AndreasHeine commented 4 years ago

typically you pass the int value of the varianttype into the list like:

[ ua.VariantType.UInt16, ua.VariantType.UInt32, ua.VariantType.Float ]

i am still looking in the opcua specs about that topic...

def _create_method(parent, nodeid, qname, callback, inputs, outputs):
    addnode = ua.AddNodesItem()
    addnode.RequestedNewNodeId = nodeid
    addnode.BrowseName = qname
    addnode.NodeClass = ua.NodeClass.Method
    addnode.ParentNodeId = parent.nodeid
    addnode.ReferenceTypeId = ua.NodeId(ua.ObjectIds.HasComponent)
    #node.TypeDefinition = ua.NodeId(ua.ObjectIds.BaseObjectType)
    attrs = ua.MethodAttributes()
    attrs.Description = ua.LocalizedText(qname.Name)
    attrs.DisplayName = ua.LocalizedText(qname.Name)
    attrs.WriteMask = 0
    attrs.UserWriteMask = 0
    attrs.Executable = True
    attrs.UserExecutable = True
    addnode.NodeAttributes = attrs
    results = parent.server.add_nodes([addnode])
    results[0].StatusCode.check()
    method = node.Node(parent.server, results[0].AddedNodeId)
    if inputs:
        create_property(method,
                        ua.NodeId(namespaceidx=method.nodeid.NamespaceIndex),
                        ua.QualifiedName("InputArguments", 0),
                        [_vtype_to_argument(vtype) for vtype in inputs], # <------------- below
                        varianttype=ua.VariantType.ExtensionObject,
                        datatype=ua.ObjectIds.Argument)
def _vtype_to_argument(vtype):
    if isinstance(vtype, ua.Argument): # <------------------ Case 1
        return vtype
    arg = ua.Argument()
    if isinstance(vtype, ua.VariantType): # <------------------ Case 2
        arg.DataType = ua.NodeId(vtype.value)
    else:
        arg.DataType = ua.NodeId(vtype) # <------------------ Case 3
    return arg
zerox1212 commented 4 years ago

I do not think using extension objects with methods is well tested and I'm not sure how the associated python function will react.

AndreyKl4 commented 4 years ago

@AndreasHeine @zerox1212 Thanks for answers. As far as i understood it is a bit difficult to use such structure as input argument in method: basic_struct_name = 'basic_structure' basic_struct = ua_server.create_structure(basic_struct_name) basic_struct.add_field('var1', ua.VariantType.Int32) basic_struct.add_field('var2', ua.VariantType.Boolean) basic_struct.add_field('var3', ua.VariantType.String)

    # add an advance structure which uses our basic structure
    nested_struct_name = 'nested_structure'
    nested_struct = ua_server.create_structure(nested_struct_name)
    nested_struct.add_field('var4', ua.VariantType.String)
    nested_struct.add_field('array', ua.VariantType.Int32)
    # add simple structure as field
    nested_struct.add_field('Field', basic_struct)

    basic_var = server.nodes.objects.add_variable(ua.NodeId(namespaceidx=nodeid), 'BasicStruct',
                                                        ua.Variant(None, ua.VariantType.Null),
                                                        datatype=basic_struct.data_type)
    basic_var = ua.Argument()
    basic_var.Name = 'Test_structure'
    basic_var.DataType = basic_struct.data_type
    basic_var.ValueRank = -3 
    basic_var.ArrayDimensions = 0

   node_methods.add_method(nodeid, 'simple_callback', call_back ,[basic_var], []) 

For my project I need to generate such nested structures in my server and after that to use them as inputs arguments in my methods. If i use all input arguments with out nested structure (flat), it will be difficult to find out in Client which parameter is responsible for what, because of big number of input arguments=( That is why i want to create first of all a nested structure and after that to use it as input argument. Perhaps smb has an idea in what way i can solve it=)

AndreasHeine commented 3 years ago

@AndreyKl4 @zerox1212

https://github.com/FreeOpcUa/python-opcua/pull/1158

lmundeja commented 3 years ago

I have implemented custom structures in one of my projects. You can have a look and check if that helps you. https://github.com/lmundeja/OPCUA-Webshop-MiniFactory/tree/main/OPCUA_Modbus