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 write to UDT #116

Open
ramezanifar opened this issue Mar 15, 2020 · 7 comments
Open

How to write to UDT #116

ramezanifar opened this issue Mar 15, 2020 · 7 comments

Comments

@ramezanifar
Copy link

Hi. I have few questions:
Q1)
Can you please provide an example how to write to a tag of type UDT having the values as an array of bytes?
There is an example how to read from a timer. I used it to read from a UDT and then tried to write it back to the same tag with the value I got from read (ret.Value) but it throws an error.

Q2)
In one of your examples you mention if the data type is provided, read and write become faster.
If I read from a tag of type UDT, what type should I use? I tried 163= SINT and 160= STRUCT
but it failed.

Q3) When reading from a UDT tag, the number of bytes returned are two more than that of UDT definition. Is this expected?

Thank you in advance

@dmroeder
Copy link
Owner

  1. It is not currently possible to write UDT's directly.
  2. Providing the data type only works for the base data types (SINT, DINT, STRING, REAL, etc).
  3. The first 2 bytes in the response is the data type.

@ramezanifar
Copy link
Author

Could you please consider this (write to UDT) as a new feature?
That would be a big help.
Thank you

@kyle-github
Copy link

I have had success writing UDTs in my library. I cheat. Before the first write, I make sure I do a read. I copy the type bytes into local memory and send them back unchanged on write. The base types like DINT take two bytes for type info. But UDTs and some other types take at least 4.

Here is the C code (data is a pointer to bytes that points to the first byte of the type info in the returned read response):

            /* the first byte of the response is a type byte. */
            pdebug(DEBUG_DETAIL, "type byte = %d (%x)", (int)*data, (int)*data);

            /* handle the data type part.  This can be long. */

            /* check for a simple/base type */
            if ((*data) >= AB_CIP_DATA_BIT && (*data) <= AB_CIP_DATA_STRINGI) {
                /* copy the type info for later. */
                if (tag->encoded_type_info_size == 0) {
                    tag->encoded_type_info_size = 2;
                    mem_copy(tag->encoded_type_info, data, tag->encoded_type_info_size);
                }

                /* skip the type byte and zero length byte */
                data += 2;
            } else if ((*data) == AB_CIP_DATA_ABREV_STRUCT || (*data) == AB_CIP_DATA_ABREV_ARRAY ||
                       (*data) == AB_CIP_DATA_FULL_STRUCT || (*data) == AB_CIP_DATA_FULL_ARRAY) {
                /* this is an aggregate type of some sort, the type info is variable length */
                int type_length = *(data + 1) + 2;  /*
                                                       * MAGIC
                                                       * add 2 to get the total length including
                                                       * the type byte and the length byte.
                                                       */

                /* check for extra long types */
                if (type_length > MAX_TAG_TYPE_INFO) {
                    pdebug(DEBUG_WARN, "Read data type info is too long (%d)!", type_length);
                    rc = PLCTAG_ERR_TOO_LARGE;
                    break;
                }

                /* copy the type info for later. */
                if (tag->encoded_type_info_size == 0) {
                    tag->encoded_type_info_size = type_length;
                    mem_copy(tag->encoded_type_info, data, tag->encoded_type_info_size);
                }

                data += type_length;

It is a pain. I cannot remember where I first saw this logic. Arrays and UDTs usually have a type ID that is two bytes (I think this corresponds to the type ID that you get when enumerating the tags in a PLC) but it can be a full description with many, many bytes. I have not seen that but it is possible apparently.

What you see in the packet is that a UDT will generally have 4 type bytes. The first one is an indicator that the type is a structure. The next one contains the number of bytes of the type ID. Then you get two bytes of type ID. Total of four. The first two are actually an INT with various bit fields but it works out as above. I have not checked to see if the type bytes you get when reading a tag are the same as the ones you get when listing out tags.

@TheFern2
Copy link
Collaborator

TheFern2 commented Mar 16, 2020

I know we've been down this road many many times about UDTs in many issues opened in the past.

#45
#6

and prob many more...

@dmroeder would you suggest that the best solution for @ramezanifar right now as the code stands is to parse the xml from the L5X file for the UDTs, and then use those tags with correct data type, provided they are base data types, if another UDT is within the UDT then it becomes more problematic.

@ramezanifar the biggest problem about implementing UDT's logic, is that people will have to learn how to unpack and pack byte data as shown in the timer example, that alone can open up a big can of worms in terms of maintainability.

@ramezanifar
Copy link
Author

I will be happy to pass the byte array because I already know the structure of my UDT.
I attempted to read a UDT and just send it back to the same tag but I got an error.

@TheFern2
Copy link
Collaborator

I will be happy to pass the byte array because I already know the structure of my UDT.
I attempted to read a UDT and just send it back to the same tag but I got an error.

Right, _writeTag method is not meant for UDTs, hence the exception. The only UDT operation in pylogix is to get UDT names, which is _getUDT

Pull Requests are welcome.

@ramezanifar
Copy link
Author

ramezanifar commented Mar 23, 2020

I have had success writing UDTs in my library. I cheat. Before the first write, I make sure I do a read. I copy the type bytes into local memory and send them back unchanged on write. The base types like DINT take two bytes for type info. But UDTs and some other types take at least 4.

Here is the C code (data is a pointer to bytes that points to the first byte of the type info in the returned read response):

            /* the first byte of the response is a type byte. */
            pdebug(DEBUG_DETAIL, "type byte = %d (%x)", (int)*data, (int)*data);

            /* handle the data type part.  This can be long. */

            /* check for a simple/base type */
            if ((*data) >= AB_CIP_DATA_BIT && (*data) <= AB_CIP_DATA_STRINGI) {
                /* copy the type info for later. */
                if (tag->encoded_type_info_size == 0) {
                    tag->encoded_type_info_size = 2;
                    mem_copy(tag->encoded_type_info, data, tag->encoded_type_info_size);
                }

                /* skip the type byte and zero length byte */
                data += 2;
            } else if ((*data) == AB_CIP_DATA_ABREV_STRUCT || (*data) == AB_CIP_DATA_ABREV_ARRAY ||
                       (*data) == AB_CIP_DATA_FULL_STRUCT || (*data) == AB_CIP_DATA_FULL_ARRAY) {
                /* this is an aggregate type of some sort, the type info is variable length */
                int type_length = *(data + 1) + 2;  /*
                                                       * MAGIC
                                                       * add 2 to get the total length including
                                                       * the type byte and the length byte.
                                                       */

                /* check for extra long types */
                if (type_length > MAX_TAG_TYPE_INFO) {
                    pdebug(DEBUG_WARN, "Read data type info is too long (%d)!", type_length);
                    rc = PLCTAG_ERR_TOO_LARGE;
                    break;
                }

                /* copy the type info for later. */
                if (tag->encoded_type_info_size == 0) {
                    tag->encoded_type_info_size = type_length;
                    mem_copy(tag->encoded_type_info, data, tag->encoded_type_info_size);
                }

                data += type_length;

It is a pain. I cannot remember where I first saw this logic. Arrays and UDTs usually have a type ID that is two bytes (I think this corresponds to the type ID that you get when enumerating the tags in a PLC) but it can be a full description with many, many bytes. I have not seen that but it is possible apparently.

What you see in the packet is that a UDT will generally have 4 type bytes. The first one is an indicator that the type is a structure. The next one contains the number of bytes of the type ID. Then you get two bytes of type ID. Total of four. The first two are actually an INT with various bit fields but it works out as above. I have not checked to see if the type bytes you get when reading a tag are the same as the ones you get when listing out tags.

Thank you for the sample.
I created a UDT that has one DINT with value one.
Reading the tag:

tag = 'Program:Main.test'      
ret = comm.Read(tag)      
print(ret) 

Result:
Program:Main.test b'\xaaz\x01\x00\x00\x00' Success
So as pointed out by you all it has two bytes for the data type.
I converted the result to a list and wrote it into the tag

Value = ['\xaa','z','\x01','\x00','\x00','\x00']  
ret = comm.Write(tag,Value)  
print(ret)

Result:
Program:Main.test ['ª', 'z', '\x01', '\x00', '\x00', '\x00'] Unknown error 255

So PLC did not like it!

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

No branches or pull requests

4 participants