libplctag / libplctag.NET

A .NET wrapper for libplctag.
https://libplctag.github.io/
Mozilla Public License 2.0
194 stars 50 forks source link

Handle string tag length mismatch and retrieve data #372

Closed chodurkhyun closed 1 month ago

chodurkhyun commented 1 month ago

In a case where the length of a string tag is larger than the actual data it contains, an ArgumentOutOfRangeException is thrown. To handle this case, I made a small modification to retrieve the data.

I encountered a 'string12' tag that actually contains a string of length 10. When I asked a PLC programmer about this, he told me they're just following the MES. According to him, if it turns out to be their fault or the MES's, it makes sense for us to apply a defensive patch.

timyhac commented 1 month ago

It is quite normal for strings to have a smaller length than their capacity - but I'm wondering why the plc_tag_get_string_length has returned something different to what plc_tag_get_string has returned.

Have you set the tag attributes up to match the UDT definition?

chodurkhyun commented 1 month ago

Yes, I have attempted to configure the tag attributes to match the UDT and have also individually modified all the attributes mentioned in the wiki page(https://github.com/libplctag/libplctag/wiki/Tag-String-Attributes#optional-generic-string-attributes) to retrieve the data from that string tag. After some experimentation, I have discovered that applying the default attribute settings (i.e., just using the TagString class) and adjusting the SubString method's argument provides the most suitable solution for my situation.

In addition to the 'string12' tag, I also need to read data from 'string4' and 'string10' tags. The configuration I mentioned earlier works for all of them.

Based on the information provided by a PLC programmer, it appears that the PLC program is simply transferring the data received from the MES to the corresponding tag. Therefore, I suspect that the data originating from the MES includes a word count of 12 but actually contains a string of length 10.

chodurkhyun commented 1 month ago

I conducted an additional experiment. Below are the raw bytes obtained using the GetBuffer method: 12 0 0 0 65 66 67 100 49 50 51 52 53 54 0 0 Regardless of whether the zero_terminated attribute is set to true or false, the native plc_tag_get_string method returns a 10-length string. In this case, I actually want to obtain a 10-length string without performing any post-processing.

kyle-github commented 1 month ago

I conducted an additional experiment. Below are the raw bytes obtained using the GetBuffer method: 12 0 0 0 65 66 67 100 49 50 51 52 53 54 0 0 Regardless of whether the zero_terminated attribute is set to true or false, the native plc_tag_get_string method returns a 10-length string. In this case, I actually want to obtain a 10-length string without performing any post-processing.

The returned value looks correct. If this is a string with a 4-byte count word and 12 character bytes, that is what you are getting. However, there are zero bytes padding the end of the string. For whatever reason, the PLC thinks that the string really is that long. Is the PLC code doing some padding?

In this case, can you run this with the debug level set to 4 and capture the output? Hopefully that will show me what is happening with plc_tag_get_string_length() and plc_tag_get_string() and why they behave differently. Note that some of this might be due to the zero padding the string.

timyhac commented 1 month ago

I think plc_tag_get_string() is doing what is expected:

The string is copied as a C-style string regardless of how it is stored in the PLC. ---- from https://github.com/libplctag/libplctag/wiki/API#reading-a-string

But we'd expect that plc_tag_get_string_length() returns 10 instead of 12.

The MES is corrupting the tag by only writing to the DATA field and not updating the LENGTH field, but I think you should still be able to explicitly tell libplctag to use null-terminated strings instead of using a counted string but still have some count bytes..

I think the configuration would be:

    str_count_word_bytes=4
    str_is_byte_swapped=0
    str_is_counted=0
    str_is_fixed_length=1
    str_is_zero_terminated=1
    str_max_capacity=12
    str_pad_bytes=0
    str_total_length=16
chodurkhyun commented 1 month ago

I conducted an additional experiment. Below are the raw bytes obtained using the GetBuffer method: 12 0 0 0 65 66 67 100 49 50 51 52 53 54 0 0 Regardless of whether the zero_terminated attribute is set to true or false, the native plc_tag_get_string method returns a 10-length string. In this case, I actually want to obtain a 10-length string without performing any post-processing.

The returned value looks correct. If this is a string with a 4-byte count word and 12 character bytes, that is what you are getting. However, there are zero bytes padding the end of the string. For whatever reason, the PLC thinks that the string really is that long. Is the PLC code doing some padding?

In this case, can you run this with the debug level set to 4 and capture the output? Hopefully that will show me what is happening with plc_tag_get_string_length() and plc_tag_get_string() and why they behave differently. Note that some of this might be due to the zero padding the string.

@kyle-github Yes, the tag consists of a 4-byte count word and 12 character bytes. I don't think the PLC code is doing any padding. When I first encountered this situation, I asked a PLC programmer to pad some bytes to create a 12-length string. However, he rejected the request and stated, "I must only transfer the data from the MES." I have attached the level-4 log.

string_tag_length.log

chodurkhyun commented 1 month ago

@timyhac First of all, I want to thank you for your recommendation. The configuration you suggested works perfectly! I had initially thought that str_is_counted needed to be set to 1 if I wanted to use str_count_word_bytes.

However, I'm now wondering if using this configuration is better than the code I've slightly modified. As I mentioned earlier, I have other string tags where the LENGTH field is not corrupted. For the sake of code consistency, I would prefer to use a single setup for all of them. To achieve this, I would need to skip every LENGTH field, which I'm not particularly eager to do. What's your opinion on this matter? I'd really appreciate your input on the best approach to handle this situation.

timyhac commented 1 month ago

I've been thinking about what the "correct" interpretation of this scenario should be:

The data in the tag (and we assume it is correct and uncorrupted) -> 12 0 0 0 65 66 67 100 49 50 51 52 53 54 0 0 Tag attributes -> counted string, 4 byte count, 12 bytes of capacity, 16 bytes total size, not zero terminated

I think the literal interpretation would be for the string value to be ABCd123456\x00\x00 - with the two null bytes still included at the end. However, we know that libplctag core will copy the string as a C-style strings (i.e. they are null terminated).

The string is copied as a C-style string regardless of how it is stored in the PLC.

So I think the change you've suggested is consistent with the underlying libplctag API and I'll merge this change.

For versioning - I believe this will be a patch rather than a minor version bump.


If the main issue is having different configurations for each type of tag I don't think this change will really solve your problem - you'll still need have a unique configuration for every type of tag (not necessarily every tag, just every unique tag type - at some point, something in the codebase needs to know how the data is encoded)

chodurkhyun commented 1 month ago

Thank you for considering my problem and my suggestion. Your recommendations and comments (including the different configuration problem) are very helpful, and I now have a better understanding of libplctag. I would also like to thank you for developing and maintaining this great library!