Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to get data from a new_struct datatype received in a @uamethod #1756

Open
shineworld opened this issue Dec 4, 2024 · 1 comment
Open

Comments

@shineworld
Copy link

shineworld commented Dec 4, 2024

Hi all,
I'm writing an OPC UA Server using latest version 1.1.5.

The server define a new structure using the:
from asyncua.common.structures104 import new_struct, new_enum, new_struct_field

method to create and add new structure type to OPC UA Server

        async def define_work_order_datatype(server):
            res, * = await new_struct(
                server,
                idx,
                "WorkOrderDataType",
                [
                    new_struct_field("order_locked"             , ua.VariantType.Boolean),
                    new_struct_field("order_priority"           , ua.VariantType.Byte),
                    new_struct_field("job_order_code"           , ua.VariantType.String),
                    new_struct_field("customer_code"            , ua.VariantType.String),
                    new_struct_field("item_code"                , ua.VariantType.String),
                    new_struct_field("material_code"            , ua.VariantType.String),
                    new_struct_field("order_notes"              , ua.VariantType.String),
                    new_struct_field("used_deadline_datetime"   , ua.VariantType.Boolean),
                    new_struct_field("deadline_datetime"        , ua.VariantType.DateTime),
                    new_struct_field("file_name_1"              , ua.VariantType.String),
                    new_struct_field("pieces_per_file_1"        , ua.VariantType.Int32),
                    new_struct_field("requested_pieces_1"       , ua.VariantType.Int32),
                    new_struct_field("file_name_2"              , ua.VariantType.String),
                    new_struct_field("pieces_per_file_2"        , ua.VariantType.Int32),
                    new_struct_field("requested_pieces_2"       , ua.VariantType.Int32),
                    new_struct_field("file_name_3"              , ua.VariantType.String),
                    new_struct_field("pieces_per_file_3"        , ua.VariantType.Int32),
                    new_struct_field("requested_pieces_3"       , ua.VariantType.Int32),
                    new_struct_field("file_name_4"              , ua.VariantType.String),
                    new_struct_field("pieces_per_file_4"        , ua.VariantType.Int32),
                    new_struct_field("requested_pieces_4"       , ua.VariantType.Int32),
                    new_struct_field("file_name_5"              , ua.VariantType.String),
                    new_struct_field("pieces_per_file_5"        , ua.VariantType.Int32),
                    new_struct_field("requested_pieces_5"       , ua.VariantType.Int32),
                    new_struct_field("file_name_6"              , ua.VariantType.String),
                    new_struct_field("pieces_per_file_6"        , ua.VariantType.Int32),
                    new_struct_field("requested_pieces_6"       , ua.VariantType.Int32),
                    new_struct_field("file_name_7"              , ua.VariantType.String),
                    new_struct_field("pieces_per_file_7"        , ua.VariantType.Int32),
                    new_struct_field("requested_pieces_7"       , ua.VariantType.Int32),
                    new_struct_field("file_name_8"              , ua.VariantType.String),
                    new_struct_field("pieces_per_file_8"        , ua.VariantType.Int32),
                    new_struct_field("requested_pieces_8"       , ua.VariantType.Int32),
                ],
            )
            return res

Create new structure data type and method which use it as argument

           # create new structure datatype and add it to OPC UA Struct node
           work_order_datatype = await define_work_order_datatype(self.server)
 
            # create and add uamethod witch use new structure datatype as argument
            a_work_order_order_code = ua.Argument()
            a_work_order_order_code.Name = "order_code"
            a_work_order_order_code.DataType = ua.NodeId(ua.ObjectIds.String)
            a_work_order_order_code.ValueRank = -1
            a_work_order_order_code.Description = ua.LocalizedText("work order: order code")
            a_work_order_data = ua.Argument()
            a_work_order_data.Name = "data"
            a_work_order_data.DataType = work_order_datatype.nodeid
            a_work_order_data.ValueRank = -1
            a_work_order_data.Description = ua.LocalizedText("work order: data")
            await self.server.nodes.objects.add_method(
                idx,
                "work_order_add",
                self.api_work_order_add,
                [a_work_order_order_code, a_work_order_data],
                [ua.VariantType.Boolean]
            )

uamethod implementation

    @uamethod
    async def api_work_order_add(self, parent, order_code: str, data):
        # ??? HOW TO GET STRUCTURE DATA ??? 
        return True

The data content is:

[Dbg]>>> data
ExtensionObject(TypeId=NodeId(Identifier=2, NamespaceIndex=2, NodeIdType=<NodeIdType.FourByte: 1>), Body=b'\x00\x00\x05\x00\x00\x0012341\x04\x00\x00\x001234\x01\x00\x00\x003\x05\x00\x00\x00sderg\x07\x00\x00\x00dfgsdfg\x00\x00@m%\xebS\xbf\x01\x08\x00\x00\x00sdfgsdfg\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00')

I was able, in some way, to get data using struct but the code is terrible and complex to maintain:

import struct
from asyncua.ua import ua

@uamethod
async def api_work_order_add(self, parent, order_code: str, data):
    try:
        # Stampa informazioni di debug
        print("ExtensionObject TypeId:", data.TypeId)
        print("Body hex:", data.Body.hex())
        print("Body length:", len(data.Body))

        # Punto di partenza per la lettura
        offset = 0

        # Funzione di supporto per leggere stringhe
        def read_string(body, start):
            # Legge la lunghezza della stringa (4 byte)
            str_len = struct.unpack('<i', body[start:start+4])[0]
            start += 4
            
            # Se la lunghezza è -1, è una stringa vuota
            if str_len == -1:
                return '', start
            
            # Legge la stringa
            string_value = body[start:start+str_len].decode('utf-8')
            return string_value, start + str_len

        # Lettura campi booleani e byte
        order_locked = data.Body[offset] == 1
        offset += 1
        
        order_priority = data.Body[offset]
        offset += 1

        # Lettura stringhe
        job_order_code, offset = read_string(data.Body, offset)
        customer_code, offset = read_string(data.Body, offset)
        item_code, offset = read_string(data.Body, offset)
        material_code, offset = read_string(data.Body, offset)
        order_notes, offset = read_string(data.Body, offset)

        # Lettura booleano deadline
        used_deadline_datetime = data.Body[offset] == 1
        offset += 1

        # Lettura datetime (64-bit intero che rappresenta i tick di Windows)
        deadline_datetime_raw = struct.unpack('<Q', data.Body[offset:offset+8])[0]
        # Converti i tick di Windows in datetime di Python se necessario
        deadline_datetime = ua.datetime_from_win_filetime(deadline_datetime_raw) if deadline_datetime_raw != 0 else None
        offset += 8

        # Lettura dettagli file
        file_details = []
        for _ in range(8):
            file_name, offset = read_string(data.Body, offset)
            pieces_per_file = struct.unpack('<i', data.Body[offset:offset+4])[0]
            offset += 4
            requested_pieces = struct.unpack('<i', data.Body[offset:offset+4])[0]
            offset += 4
            
            file_details.append({
                'file_name': file_name,
                'pieces_per_file': pieces_per_file,
                'requested_pieces': requested_pieces
            })

        # Stampa i dati decodificati
        print("Order Locked:", order_locked)
        print("Order Priority:", order_priority)
        print("Job Order Code:", job_order_code)
        print("Customer Code:", customer_code)
        print("Item Code:", item_code)
        print("Material Code:", material_code)
        print("Order Notes:", order_notes)
        print("Used Deadline:", used_deadline_datetime)
        print("Deadline Datetime:", deadline_datetime)
        
        # Stampa dettagli dei file
        for i, file_info in enumerate(file_details, 1):
            if file_info['file_name']:
                print(f"File {i}:")
                print(f"  Name: {file_info['file_name']}")
                print(f"  Pieces per File: {file_info['pieces_per_file']}")
                print(f"  Requested Pieces: {file_info['requested_pieces']}")
        
        return True
    except Exception as e:
        print(f"Errore nella decodifica dei dati: {e}")
        import traceback
        traceback.print_exc()
        return False

There is some better and ready to use way to get info from "data" using library functions ?

@shineworld
Copy link
Author

After a full day of try and suggestion by ChatCPT and ClaudAI (without resoults), for totally case of life I've found the solution.
This can be useful to other so I place here.

When I create the new_struct based data type in the method argument I get always a data of basic type with raw data.
But if in new_struct type I call await load_custom_struct(new_data_type) the method argument become an auto-generated class with all published data:

        async def define_work_order_datatype():
            res, _ = await new_struct(
                self.server,
                idx,
                "WorkOrderDataType",
                [
                    new_struct_field("order_locked"             , ua.VariantType.Boolean),
                    new_struct_field("order_priority"           , ua.VariantType.Byte),
                    new_struct_field("job_order_code"           , ua.VariantType.String),
                    new_struct_field("customer_code"            , ua.VariantType.String),
                    new_struct_field("item_code"                , ua.VariantType.String),
                    new_struct_field("material_code"            , ua.VariantType.String),
                    new_struct_field("order_notes"              , ua.VariantType.String),
                    new_struct_field("used_deadline_datetime"   , ua.VariantType.Boolean),
                    new_struct_field("deadline_datetime"        , ua.VariantType.DateTime),
                    new_struct_field("file_name_1"              , ua.VariantType.String),
                    new_struct_field("pieces_per_file_1"        , ua.VariantType.Int32),
                    new_struct_field("requested_pieces_1"       , ua.VariantType.Int32),
                    new_struct_field("file_name_2"              , ua.VariantType.String),
                    new_struct_field("pieces_per_file_2"        , ua.VariantType.Int32),
                    new_struct_field("requested_pieces_2"       , ua.VariantType.Int32),
                    new_struct_field("file_name_3"              , ua.VariantType.String),
                    new_struct_field("pieces_per_file_3"        , ua.VariantType.Int32),
                    new_struct_field("requested_pieces_3"       , ua.VariantType.Int32),
                    new_struct_field("file_name_4"              , ua.VariantType.String),
                    new_struct_field("pieces_per_file_4"        , ua.VariantType.Int32),
                    new_struct_field("requested_pieces_4"       , ua.VariantType.Int32),
                    new_struct_field("file_name_5"              , ua.VariantType.String),
                    new_struct_field("pieces_per_file_5"        , ua.VariantType.Int32),
                    new_struct_field("requested_pieces_5"       , ua.VariantType.Int32),
                    new_struct_field("file_name_6"              , ua.VariantType.String),
                    new_struct_field("pieces_per_file_6"        , ua.VariantType.Int32),
                    new_struct_field("requested_pieces_6"       , ua.VariantType.Int32),
                    new_struct_field("file_name_7"              , ua.VariantType.String),
                    new_struct_field("pieces_per_file_7"        , ua.VariantType.Int32),
                    new_struct_field("requested_pieces_7"       , ua.VariantType.Int32),
                    new_struct_field("file_name_8"              , ua.VariantType.String),
                    new_struct_field("pieces_per_file_8"        , ua.VariantType.Int32),
                    new_struct_field("requested_pieces_8"       , ua.VariantType.Int32),
                ],
            )

            # TAKE CARE
            # =========
            # This call is FUNDAMENTAL for methods that use this data type to get an autogenerated WorkOrderTypeData
            # class from the server as an argument instead of receiving a generic class with the data as a stream of
            # bytes that is very difficult to interpret.
            #
            await load_custom_struct(res)
            return res

so in method I have direct data:

    @uamethod
    async def api_work_order_add(self, parent, order_code: str, data):
        try:
            print(f'order code              : {order_code}')
            print(f'order_locked            : {data.order_locked}')
            print(f'order_priority          : {data.order_priority}')
            print(f'job_order_code          : {data.job_order_code}')
            print(f'customer_code           : {data.customer_code}')
            print(f'item_code               : {data.item_code}')
            print(f'material_code           : {data.material_code}')
            print(f'order_notes             : {data.order_notes}')
            print(f'used_deadline_datetime  : {data.used_deadline_datetime}')
            print(f'deadline_datetime       : {data.deadline_datetime}')
            print(f'file_name_1             : {data.file_name_1}')
            print(f'pieces_per_file_1       : {data.pieces_per_file_1}')
            print(f'requested_pieces_1      : {data.requested_pieces_1}')
            print(f'file_name_2             : {data.file_name_2}')
            print(f'pieces_per_file_2       : {data.pieces_per_file_2}')
            print(f'requested_pieces_2      : {data.requested_pieces_2}')
            print(f'file_name_3             : {data.file_name_3}')
            print(f'pieces_per_file_3       : {data.pieces_per_file_3}')
            print(f'requested_pieces_3      : {data.requested_pieces_3}')
            print(f'file_name_4             : {data.file_name_4}')
            print(f'pieces_per_file_4       : {data.pieces_per_file_4}')
            print(f'requested_pieces_4      : {data.requested_pieces_4}')
            print(f'file_name_5             : {data.file_name_5}')
            print(f'pieces_per_file_5       : {data.pieces_per_file_5}')
            print(f'requested_pieces_5      : {data.requested_pieces_5}')
            print(f'file_name_6             : {data.file_name_6}')
            print(f'pieces_per_file_6       : {data.pieces_per_file_6}')
            print(f'requested_pieces_6      : {data.requested_pieces_6}')
            print(f'file_name_7             : {data.file_name_7}')
            print(f'pieces_per_file_7       : {data.pieces_per_file_7}')
            print(f'requested_pieces_7      : {data.requested_pieces_7}')
            print(f'file_name_8             : {data.file_name_8}')
            print(f'pieces_per_file_8       : {data.pieces_per_file_8}')
            print(f'requested_pieces_8      : {data.requested_pieces_8}')
            return True
        except
            return False

Wonderful library !!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant