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

SLC Strings - PLCTAG_ERR_TOO_SMALL #58

Closed
IanRobo75 opened this issue Jul 20, 2020 · 75 comments · Fixed by #83
Closed

SLC Strings - PLCTAG_ERR_TOO_SMALL #58

IanRobo75 opened this issue Jul 20, 2020 · 75 comments · Fixed by #83

Comments

@IanRobo75
Copy link

Is reading strings from a MicroLogix (SLC plc type) supported?

I'm reading from and writing to other data types fine, but when reading a string, e.g. ST102:0, I get the error PLCTAG_ERR_TOO_SMALL. Element size is 88, so seems right, confused why the error.

@timyhac
Copy link
Collaborator

timyhac commented Jul 20, 2020

Hi @IanRobo75 - thanks for trying this wrapper!

The wrapper should be able to support anything that the native runtimes support, so it could be possible libplctag native doesn't support this - I'm not really sure.

It is possible that the wrapper has some issues, you can make use of the libplctag.NativeImport package that provides raw access to the libplctag native functionality without any abstractions over the top - could be useful to try this.

Also, are you using the latest prerelease? These packages are still in alpha.

MicroLogic and SLC are different CPU types, could you try with both and see what the result is?
https://github.com/libplctag/libplctag/wiki/Tag-String-Attributes

@IanRobo75
Copy link
Author

IanRobo75 commented Jul 20, 2020 via email

@kyle-github
Copy link
Member

kyle-github commented Jul 20, 2020 via email

@IanRobo75
Copy link
Author

Sorry for the email response. One thing to try is a different size. I think strings on PCCC-based PLCs are either 82 or 84 bytes, not 88. Best, Kyle

On Mon, Jul 20, 2020, 12:26 PM IanRobo75 @.> wrote: I see that the wrapper does not expose the MicroLogix CPU Type. I’ll look at using the NativeImport and investigate. Cheers, Ian Ian Robinson Machine Safety & Automation Engineer [swarmIQ_colour 20%] From: timyhac @.> Sent: Monday, July 20, 2020 4:44 PM To: libplctag/libplctag.NET @.> Cc: IanRobo75 @.>; Mention @.***> Subject: Re: [libplctag/libplctag.NET] SLC Strings - PLCTAG_ERR_TOO_SMALL (#58) Hi @IanRobo75https://github.com/IanRobo75 - thanks for trying this wrapper! The wrapper should be able to support anything that the native runtimes support, so it could be possible libplctag native doesn't support this - I'm not really sure. It is possible that the wrapper has some issues, you can make use of the libplctag.NativeImport package that provides raw access to the libplctag native functionality without any abstractions over the top - could be useful to try this. Also, are you using the latest prerelease? These packages are still in alpha. MicroLogic and SLC are different CPU types, could you try with both and see what the result is? https://github.com/libplctag/libplctag/wiki/Tag-String-Attributes — You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub< #58 (comment)>, or unsubscribe< https://github.com/notifications/unsubscribe-auth/AIY6RY3MLTPIP5TTGOJZJPDR4PDQDANCNFSM4PB255VQ>. — You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub <#58 (comment)>, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAN4LC2OI7XVMRQR5UN2YY3R4SK5RANCNFSM4PB255VQ .

OK will do. I see the latest alpha has the MicroLogix PLC type as well, which was missing from the release version. So I'll try that as well.

@timyhac
Copy link
Collaborator

timyhac commented Jul 20, 2020

This is interesting - it means we will have to have different marshallers for different CPU types.

@jkoplo

@IanRobo75
Copy link
Author

OK, so I believe the base library does not easily support strings for PLC5/ SLC/ MicroLogix comms. I can read a string if I put the correct length in for the string (i.e. if the string in the PLC is "ABC" and I set the tag length as 3 then it works). Getting the length attribute dynamically is too hard for me and not worth the effort.
For reference - I can read I, O, N files as integer (16 bit), and L files as Long (32 bit), but they have to have the N prefix (e.g. N100:1, N9:3 (for L9:3), N0:4 (for O:4), N1:5 (for I:5). B and F files are read as expected (e.g. B3:6, F8:7).

My next task is to communicate with a CompactLogix, I expect strings will be fine there.

@kyle-github
Copy link
Member

So reads of DINT are not working? That's not good! Can you please create a dump when trying to read a L file (debug level 4)? I can try to see if there is something obvious about the error code. I only have a PLC/5 to test on, so all that is guess work on my part.

Strings are quite painful. However, I can see what I can do to make the reads easier in the core library. With Control/Compact Logix systems, I just allocate the tag buffer as I am reading in the data. For PCCC-based systems (MicroLogix, SLC, PLC/5), I do not. I will see if there is anything I can do to make that work as well. Then you could read a string in. The problem is when you want to read a string in and then write out a longer string. Then there is no buffer space.

I may need to special case strings. They are not an atomic type like INT and they are not an array and they are not a UDT. And they seem to be quite different on Micro800 PLCs too.

@kyle-github
Copy link
Member

This is interesting - it means we will have to have different marshallers for different CPU types.

Thank AB. There are three string formats that I am aware of:

  1. ControlLogix/CompactLogix: 88 bytes with a 4-byte length count and 82 bytes of data and two bytes of padding.
  2. PCCC PLCs: 82 bytes of data and 2-byte count word. Total of 84 bytes. I think. I'll need to double check.
  3. Micro800: one byte count word and up to 255 bytes of data, I think. It really is not clear and I have only had about five minutes of time with one.

AB is NOT a single family. It is at least three. Multiple times I have come close to splitting the code into three completely separate sections for the different PLC types. And there are significant differences between PLC/5, SLC and MicroLogix. The latter two are quite similar but seem to support different maximum packet sizes. But commands for a PLC/5 rarely work for SLC/MicroLogix and vice versa.

@timyhac
Copy link
Collaborator

timyhac commented Jul 21, 2020

Thanks Kyle!

@IanRobo75 - RSLogix provides the tag size when you browse the controller data types, at least for CompactLogix, I'm not sure how this works for other CPU types (or other versions of RSLogix for that matter).
image

@kyle-github - One thing I was thinking about doing was providing some guidance on how to reverse-engineer the binary format of a tag (assuming you already know the tag size). I've ended up using a combination of Rockwell manuals and hex-format viewing to get this working for an array of UDTs with embedded Structures - seems to work OK but it's not straightforward.

@IanRobo75
Copy link
Author

IanRobo75 commented Jul 21, 2020 via email

@IanRobo75
Copy link
Author

IanRobo75 commented Jul 21, 2020 via email

@kyle-github
Copy link
Member

@IanRobo75 , for some reason that is not coming through for me.

Strings are clearly a problem. They are a special UDT that is common enough that there should probably be a way to deal with them that is not so painful.

Right now, the only way to deal with them is to allocate the full 82 characters plus space for the length word. But on ControlLogix (and Micro800), we do not actually know that we are dealing with STRING types until we read the tag. At least with PCCC PLCs, you have the data file type, so the tag size could be set initially.

I'll have to think about this a bit.

@timyhac
Copy link
Collaborator

timyhac commented Jul 21, 2020

The case where String tags are static in size would be relatively easy to handle in the wrapper, the Encoder/Decoder/Marshaller just needs to know which Cpu type its targeting, and change its logic accordingly.

My understanding (based on some tests of string arrays), is that even though the LEN property tells us how many characters are in the string, the size of the entire tag is always 88 bytes, so if LEN was 12, then there would be 12 ASCII characters, and then 70 null characters.

If the actual size of the tag changes depending on the data in it, then that would be tricky... It would have to be two round trips.... Hmmm...

@IanRobo75
Copy link
Author

IanRobo75 commented Jul 21, 2020 via email

@kyle-github
Copy link
Member

I seem to remember cases where some PCCC PLCs return just the string data and the length without any padding. Does anyone have a MicroLogix or SLC to test that theory?

If that is true, then the core library needs to have some additions in order to recognize string types and behave correctly. For *Logix PLCs, you get the whole string buffer, so there is always space. But in cases where you do not get the whole buffer the library will need to change behavior because PCCC PLCs do not return a status indicating that there is more data left. You just get what you get in the packet.

@kyle-github
Copy link
Member

I definitely want to avoid multiple round trips, so the important check here is to see if anything returns variable length results.

@kyle-github
Copy link
Member

This is interesting - it means we will have to have different marshallers for different CPU types.

Thank AB. There are three string formats that I am aware of:

  1. ControlLogix/CompactLogix: 88 bytes with a 4-byte length count and 82 bytes of data and two bytes of padding.
  2. PCCC PLCs: 82 bytes of data and 2-byte count word. Total of 84 bytes. I think. I'll need to double check.
  3. Micro800: one byte count word and up to 255 bytes of data, I think. It really is not clear and I have only had about five minutes of time with one.

AB is NOT a single family. It is at least three. Multiple times I have come close to splitting the code into three completely separate sections for the different PLC types. And there are significant differences between PLC/5, SLC and MicroLogix. The latter two are quite similar but seem to support different maximum packet sizes. But commands for a PLC/5 rarely work for SLC/MicroLogix and vice versa.

@IanRobo75
Copy link
Author

IanRobo75 commented Jul 21, 2020 via email

@IanRobo75
Copy link
Author

IanRobo75 commented Jul 21, 2020 via email

@timyhac
Copy link
Collaborator

timyhac commented Jul 21, 2020

Thanks for checking this Ian. Very interesting that the characters are byte swapped!

I have no experience with MicroLogix - are "N" tags 16 bits (element size = 2)?

Update: 16bits not bytes

@timyhac
Copy link
Collaborator

timyhac commented Jul 21, 2020

@IanRobo75 - please not I've just released a new (still alpha) version of NativeImport. Main difference for you is the names of the methods have changed to 100% reflect the C API.
Also included are constants (StatusCodes etc) so you don't have to define these yourself.

@kyle-github
Copy link
Member

kyle-github commented Jul 21, 2020

@IanRobo75, thanks for the checks.

I had a report a while back of byte swapped character pairs in strings but I never was able to replicate it. That says to me that I need to treat strings as a completely different data type in the core library. Note that strings are different across different PLCs.

I had to update some PLC/5 code recently because the words within a REAL were byte swapped.

@IanRobo75
Copy link
Author

IanRobo75 commented Jul 21, 2020 via email

@IanRobo75
Copy link
Author

IanRobo75 commented Jul 22, 2020

@IanRobo75 - please not I've just released a new (still alpha) version of NativeImport. Main difference for you is the names of the methods have changed to 100% reflect the C API.
Also included are constants (StatusCodes etc) so you don't have to define these yourself.

Yep, makes sense, I've updated my ML test example accordingly.

Still can't read L9:5 though (using libplctag), even as N9:5; get PLCTAG_ERR_BAD_PARAM.

Here's the level 4 debug...

2389-06-22 12:11:25.236 thread(1) tag(0) INFO find_tag_create_func:95 Matched protocol=ab_eip
2389-06-22 12:11:25.236 thread(1) tag(0) INFO ab_tag_create:197 Starting.
2389-06-22 12:11:25.236 thread(1) tag(0) INFO rc_alloc_impl:111 Starting, called from ab_tag_create:204
2389-06-22 12:11:25.236 thread(1) tag(0) INFO rc_alloc_impl:130 Done
2389-06-22 12:11:25.236 thread(1) tag(0) DETAIL rc_alloc_impl:135 Returning memory pointer 011965B8
2389-06-22 12:11:25.236 thread(1) tag(0) DETAIL ab_tag_create:211 tag=011965B8
2389-06-22 12:11:25.236 thread(1) tag(0) DETAIL get_plc_type:1695 Found SLC 500 PLC.
2389-06-22 12:11:25.236 thread(1) tag(0) DETAIL get_plc_type:1695 Found SLC 500 PLC.
2389-06-22 12:11:25.236 thread(1) tag(0) DETAIL session_find_or_create:226 Starting
2389-06-22 12:11:25.236 thread(2) tag(0) INFO tag_tickler_func:187 Starting.
2389-06-22 12:11:25.236 thread(1) tag(0) DETAIL session_find_or_create:250 Creating new session.
2389-06-22 12:11:25.236 thread(1) tag(0) INFO session_create_unsafe:432 Starting
2389-06-22 12:11:25.236 thread(1) tag(0) DETAIL session_create_unsafe:434 Warning: not using passed port 44818
2389-06-22 12:11:25.236 thread(1) tag(0) DETAIL session_create_unsafe:439 Session should not use connected messaging.
2389-06-22 12:11:25.236 thread(1) tag(0) INFO rc_alloc_impl:111 Starting, called from session_create_unsafe:442
2389-06-22 12:11:25.236 thread(1) tag(0) INFO rc_alloc_impl:130 Done
2389-06-22 12:11:25.236 thread(1) tag(0) DETAIL rc_alloc_impl:135 Returning memory pointer 090824E0
2389-06-22 12:11:25.236 thread(1) tag(0) DETAIL add_session_unsafe:306 Starting
2389-06-22 12:11:25.236 thread(1) tag(0) DETAIL add_session_unsafe:316 Done
2389-06-22 12:11:25.236 thread(1) tag(0) INFO session_create_unsafe:537 Done
2389-06-22 12:11:25.236 thread(1) tag(0) INFO session_init:553 Starting.
2389-06-22 12:11:25.236 thread(1) tag(0) DETAIL thread_create:612 Starting.
2389-06-22 12:11:25.236 thread(1) tag(0) DETAIL thread_create:646 Done.
2389-06-22 12:11:25.237 thread(1) tag(0) INFO session_init:567 Done.
2389-06-22 12:11:25.237 thread(1) tag(0) DETAIL session_find_or_create:296 Done
2389-06-22 12:11:25.237 thread(1) tag(0) DETAIL ab_tag_create:293 using session=090824E0
2389-06-22 12:11:25.237 thread(1) tag(0) DETAIL ab_tag_create:324 Setting up SLC/MicroLogix tag.
2389-06-22 12:11:25.237 thread(1) tag(0) DETAIL slc_encode_tag_name:316 Starting.
2389-06-22 12:11:25.237 thread(1) tag(0) DETAIL parse_pccc_logical_address:743 Starting.
2389-06-22 12:11:25.237 thread(1) tag(0) WARN parse_pccc_file_type:906 Bad format or unsupported logical address L9:5!
2389-06-22 12:11:25.237 thread(1) tag(0) DETAIL parse_pccc_file_type:912 Done.
2389-06-22 12:11:25.237 thread(1) tag(0) WARN parse_pccc_logical_address:747 Unable to parse PCCC-style tag for data-table type! Error PLCTAG_ERR_BAD_PARAM!
2389-06-22 12:11:25.238 thread(3) tag(0) INFO session_handler:925 Starting thread for session 090824E0
2389-06-22 12:11:25.238 thread(1) tag(0) DETAIL parse_pccc_logical_address:767 Starting.
2389-06-22 12:11:25.238 thread(1) tag(0) WARN slc_encode_tag_name:327 Unable to parse SLC logical addresss!
2389-06-22 12:11:25.238 thread(3) tag(0) DETAIL session_handler:944 in SESSION_OPEN_SOCKET state.
2389-06-22 12:11:25.238 thread(1) tag(0) WARN check_tag_name:1758 parse of SLC-style tag name L9:5 failed!
2389-06-22 12:11:25.238 thread(3) tag(0) INFO session_open_socket:583 Starting.
2389-06-22 12:11:25.238 thread(1) tag(0) INFO ab_tag_create:437 Bad tag name!
2389-06-22 12:11:25.238 thread(3) tag(0) DETAIL socket_create:835 Starting.
2389-06-22 12:11:25.238 thread(1) tag(0) WARN plc_tag_create:567 Error PLCTAG_ERR_BAD_PARAM while trying to create tag!
2389-06-22 12:11:25.238 thread(1) tag(0) DETAIL rc_dec_impl:242 Calling cleanup functions due to call at plc_tag_create:568 for 011965B8.
2389-06-22 12:11:25.238 thread(1) tag(0) INFO refcount_cleanup:256 Starting
2389-06-22 12:11:25.238 thread(1) tag(0) INFO ab_tag_destroy:753 Starting.
2389-06-22 12:11:25.238 thread(1) tag(0) DETAIL ab_tag_destroy:765 Getting ready to release tag session 090824E0
2389-06-22 12:11:25.238 thread(1) tag(0) DETAIL ab_tag_destroy:767 Removing tag from session.
2389-06-22 12:11:25.238 thread(1) tag(0) DETAIL rc_dec_impl:242 Calling cleanup functions due to call at ab_tag_destroy:768 for 090824E0.
2389-06-22 12:11:25.238 thread(1) tag(0) INFO refcount_cleanup:256 Starting
2389-06-22 12:11:25.238 thread(1) tag(0) INFO session_destroy:722 Starting.
2389-06-22 12:11:25.238 thread(1) tag(0) DETAIL remove_session:367 Starting.
2389-06-22 12:11:25.238 thread(1) tag(0) DETAIL remove_session_unsafe:343 Starting
2389-06-22 12:11:25.238 thread(1) tag(0) DETAIL remove_session_unsafe:358 Done
2389-06-22 12:11:25.238 thread(1) tag(0) DETAIL remove_session:375 Done.
2389-06-22 12:11:25.238 thread(1) tag(0) INFO session_destroy:733 Session sent 0 packets.
2389-06-22 12:11:25.240 thread(3) tag(0) DETAIL socket_create:854 Done.
2389-06-22 12:11:25.240 thread(3) tag(0) DETAIL socket_connect_tcp:874 Starting.
2389-06-22 12:11:25.249 thread(3) tag(0) DETAIL socket_connect_tcp:923 Found numeric IP address: 192.168.5.151
2389-06-22 12:11:25.250 thread(3) tag(0) DETAIL socket_connect_tcp:1014 Done.
2389-06-22 12:11:25.250 thread(3) tag(0) INFO session_open_socket:600 Done.
2389-06-22 12:11:25.250 thread(3) tag(0) DETAIL session_handler:1137 Critical block.
2389-06-22 12:11:25.256 thread(1) tag(0) INFO session_close_socket:703 Starting.
2389-06-22 12:11:25.259 thread(1) tag(0) INFO session_close_socket:711 Done.
2389-06-22 12:11:25.259 thread(1) tag(0) INFO session_destroy:806 Done.
2389-06-22 12:11:25.259 thread(1) tag(0) INFO refcount_cleanup:268 Done.
2389-06-22 12:11:25.259 thread(1) tag(0) INFO ab_tag_destroy:789 Finished releasing all tag resources.
2389-06-22 12:11:25.259 thread(1) tag(0) INFO ab_tag_destroy:791 done
2389-06-22 12:11:25.259 thread(1) tag(0) INFO refcount_cleanup:268 Done.

@timyhac
Copy link
Collaborator

timyhac commented Jul 22, 2020

Interesting - what is the full attribute string for this tag? I don't think I can help but it might clarify.

@IanRobo75
Copy link
Author

IanRobo75 commented Jul 22, 2020

Here are my results reading different MicroLogix data types with libplctag.NativeImport ...

How to read the data successfully...
O:0.0 - Read as O0:0 as element length 2
O:0.1 - Read as O0:0 as element length 4 and use bytes 2 & 3
O:1.1 - Read as O0:1 as element length 2
O:1.2 - Read as O0:1 as element length 4 and use bytes 2 & 3
I:0 - Read as I1:0
S:4 - Read as S2:4
B3:0 - Read as B3:0 [same]
T4:0.PRE - Read as T4:0.PRE [same]
C5:0.PRE - Read as C5:0.PRE [same]
N7:0 - Read as N7:0 [same]
L9:5 - !!! can't read in any form !!!
ST102:0 - Read as ST102:0 [same] (then decode bytes 0 & 1 as length, and that length of characters from byte 2 onwards)

Note all the working ones are 16-bit integers [element size 2] + the string [element size 84].

@kyle-github
Copy link
Member

Well that points to the problem!

2389-06-22 12:11:25.237 thread(1) tag(0) WARN parse_pccc_file_type:906 Bad format or unsupported logical address L9:5!

Looks like I am not handling the L type. And it looks like I do not have the data type byte I need for it. My DF1 docs are so old that I do not see a 32-bit integer type in them. I will need to hunt around to see if there is an updated version of the PCCC protocol docs.

I am using PLC/5-specific commands to read my PLC/5 and with those (but not the commands I was using!) I see the byte swapped characters as well. But the count/LEN field is little-endian. Oh how I love AB.

@IanRobo75
Copy link
Author

IanRobo75 commented Jul 22, 2020

from here... https://www.sciencedirect.com/science/article/pii/S1742287617301998

looks like the L file is file type 0x91 in the PCCC protocol...

Test Cases | Classified File-type

Data Files/New/select Type:Binary | – | New file B9 | 0x85
Data Files/New/select Type:Integer | – | New file N10 | 0x89
Data Files/New/select Type:Long | – | New file L11 | 0x91
Data Files/New/select Type:Message | – | New file MSG12 | 0x92
Data Files/New/select Type:PID | – | New file PI13 | 0x93

@timyhac
Copy link
Collaborator

timyhac commented Jul 22, 2020

@kyle-github - if you'd prefer to handle strings in the C library that is Ok, but it is relatively easy to develop marshalling logic based on Cpu type - although it would mean that all of the wrappers would need to implement this separately.

@IanRobo75
Copy link
Author

IanRobo75 commented Jul 22, 2020 via email

@kyle-github
Copy link
Member

I will be releasing the core library with the above changes for Long Int over the weekend. Hopefully @jkoplo or @timyhac can release a new version of the C# library including that soon after I release the core library.

@kyle-github
Copy link
Member

Version 2.1.10 is released. This should have support for PCCC long integers in the C core library.

@timyhac
Copy link
Collaborator

timyhac commented Jul 26, 2020

0.0.9-alpha of libplctag.NativeImport has been released that includes these binaries.

@IanRobo75 - in some testing I've found that if you set the target platform to x86 then NativeImport will not be able to interop with the x64 binary despite running on an x64 machine. This could be related to your BadImageFormatException.

@IanRobo75
Copy link
Author

IanRobo75 commented Jul 26, 2020 via email

@kyle-github
Copy link
Member

Thanks for testing!

@IanRobo75
Copy link
Author

Will the NativeImport wrapper get the string code soon-ish? And I assume the final C# wrapper would run the tag_get_string_length, then plc_tag_get_string_char for those number of characters and return the complete c# string to the user?

@kyle-github
Copy link
Member

Hi @IanRobo75, the string API is still being discussed. I have not started implementing it yet in the core library.

@IanRobo75
Copy link
Author

IanRobo75 commented Jul 27, 2020

OK.
In my main app I'm now testing libplctag 0.0.27-alpha10.
in my build folder (...\bin\Debug) I renamed libtag.dll and plctag.dll and rebuilt the app; the new files were created correctly (libplctag.dll dated Friday, and plctag.dll dated at build time).
When I run my program it adds the tag "B3:0" ok, then when adding the tag "F8:0" the program [exe] crashes, error code 0xc0000409 [= STATUS_STACK_BUFFER_OVERRUN].

The offending line...
plcReadGroups.Add(new Tag(System.Net.IPAddress.Parse(IPAddress), "", (libplctag.CpuType)CPUType, TagSize(TypeStart), CheckName(groupStart), TIMEOUT, readIndex + 1));

Note: here I'm actually creating and adding the tag to a list, but that's been working fine up to now. So the important tag part, putting the parsed parts in square brackets for display here, is...
Tag([192.168.5.151], "", [MicroLogix], 4, "F8:0", 5000, 1)

@timyhac
Copy link
Collaborator

timyhac commented Jul 27, 2020

Hi Ian - that seems to be a .NET exception - is that right?

Does it repeatedly throw the error on adding the same tag (i.e. on consecutive runs)?
Can you create that tag by itself in a standalone console application?
If you add a different tag at the same point in time (even duplicating another tag should be ok) - does it throw?
Are tags being added in parallel/multithreaded?
Are you able to create that Tag using NativeImport?

I haven't come across this error before. It seems to be something related to the native binaries..
https://devblogs.microsoft.com/oldnewthing/20190108-00/?p=100655

@timyhac
Copy link
Collaborator

timyhac commented Jul 30, 2020

Hi @IanRobo75 - I've put together a string marshalling class and would appreciate if you could take a look at the implementation before it is released.
https://github.com/libplctag/libplctag.NET/blob/generics-timyhac/src/libplctag/DataTypes/StringMarshaller.cs#L178

You can see that there is different logic for each PLC type. There isn't an implementation for Plc5, Slc500, or Micro800 - but there is for ControlLogix, LogixPccc and MicroLogix.

Any feedback you've got for this would be great.

@IanRobo75
Copy link
Author

IanRobo75 commented Aug 6, 2020

will test at some point, just really busy currently.
Also. aren't PLC5, & SLC500 PCCC anyway so would be the same as MicroLogix?

@IanRobo75
Copy link
Author

IanRobo75 commented Aug 14, 2020

OK, just getting back onto this.

Frist issue, just reading through StringMarsahhler.cs before I start....

    override public int? ElementSize
    {
        get
        {
            switch (PlcType)
            {
                case PlcType.ControlLogix: return 88;
                case PlcType.Plc5: return 88;
                case PlcType.Slc500: return 25;
                case PlcType.LogixPccc: return 84;
                case PlcType.Micro800: return 256;
                case PlcType.MicroLogix: return 256;
                default: throw new NotImplementedException();
            }
        }
    }

PLC5, SLC500, and MicroLogix all use strings of 82 characters. With the length bytes header assumedly they are all 84 bytes long, which makes sense because they all are LogixPCCC really.

I'll modify accordingly for my tests with MicroLogix.

@timyhac
Copy link
Collaborator

timyhac commented Aug 15, 2020

Thanks @IanRobo75 !

@IanRobo75
Copy link
Author

So, I've cloned the github code, libplctag/libplctag.NET, (libplctag alpha 13 & libplctag.NativeImport 11 beta).

Doing a new test referencing & using the LibPlcTag class in a forms app, just reading an integer, N7:2, from a MicroLogix...

using System;
using System.Windows.Forms;
using System.Threading;
using libplctag;
......

        var myTag = new Tag()
        {
            Name = "N7:2",
            Gateway = "192.168.5.151",
            Path = "",
            PlcType = PlcType.MicroLogix,
            Protocol = Protocol.ab_eip,
            ElementSize = 2,
            ElementCount = 1
        };

        //Check that tag gets created properly
       while (myTag.GetStatus() == Status.Pending)
            Thread.Sleep(100);

        if (myTag.GetStatus() != Status.Ok)
            throw new LibPlcTagException(myTag.GetStatus());  //<<<<<< GET THE ERROR HERE

        myTag.Read();

.....

I get the error 'libplctag.LibPlcTagException: 'ErrorNotFound'
[HResult: -2146233088 ]

However, my NativeImport 11 beta test app read data fine from the same PLC....

using libplctag.NativeImport;
...

            int elementCount = 1;
            int elementSize = int.Parse(tbSize.Text); //=2
            string tagname = tbAddress.Text;  //="N7:2"

            string path = "protocol=ab_eip" +
                "&gateway=192.168.5.151" +
                "&cpu=micrologix" +
                "&elem_size=" + elementSize.ToString() +
                "&elem_count =" + elementCount.ToString() +
                "&name=" + tagname +
                "&debug=4";

            //clear result textbox on form 
            tbResult.Clear();

            // check the library version.
            if (plctag.plc_tag_check_lib_version(VER_MAJOR, VER_MINOR, VER_PATCH) != (int)STATUS_CODES.PLCTAG_STATUS_OK)
            {
                Debug.Print("Required compatible library version 2.1.0 not available!\n");
                return;
            }

            //create tag
            Int32 tag = plctag.plc_tag_create(path, 5000);

            //check could be created
            if (tag < 0)
            {
                Debug.Print("ERROR " + plctag.plc_tag_decode_error(tag) + " : Could not create tag!\n");
                tbResult.Text += "ERROR " + plctag.plc_tag_decode_error(tag) + " : Could not create tag!" + Environment.NewLine;
                return;
            }

            //check for error
            if (plctag.plc_tag_status(tag) != (int)STATUS_CODES.PLCTAG_STATUS_OK)
            {
                Debug.Print("Error setting up tag internal state.  Error " + plctag.plc_tag_decode_error(tag) + "\n");
                plctag.plc_tag_destroy(tag);
                return;
            }

            int rc;

            if (elementSize <= 4)
            {
                rc = plctag.plc_tag_read(tag, 5000);
                if (rc != (int)STATUS_CODES.PLCTAG_STATUS_OK)
                {
                    Debug.Print("ERROR: Unable to read the data! Got error code " + rc.ToString() + " : " + plctag.plc_tag_decode_error(rc) + "\n");
                    plctag.plc_tag_destroy(tag);
                    return;
                }

            }


            Int32 iData = 0;

            switch (elementSize)
            {
                case 2:
                    iData = plctag.plc_tag_get_int16(tag, 0);
                    tbResult.Text += tagname + " data = " + iData.ToString() + Environment.NewLine;
                    break;
                case 4:
                    iData = plctag.plc_tag_get_int32(tag, 0);
                    tbResult.Text += tagname + " data = " + iData.ToString() + Environment.NewLine;
                    break;
                default: //string
                    //rc = plctag.plc_tag_get_string_Length(tag, 0);
                    break;
            }

....

@timyhac
Copy link
Collaborator

timyhac commented Aug 17, 2020

Hi @IanRobo75 - seems like the tag creation isn't working.

What is in master at the moment doesn't have a version number. If you cloned this commit then you'll also need to make a call to Initialize().

What is the attribute string generated by the library? You can set a breakpoint here to find out.

You don't need to provide a blank string if the path is intended to be unset, leaving it as null is OK.

@IanRobo75
Copy link
Author

Yes, Initialise sorted it.

@IanRobo75
Copy link
Author

IanRobo75 commented Aug 18, 2020

OK, testing reading strings...

Relevant parts of my code:
//-----------------------------------------------------------------------------

        var myTag = new Tag()
        {
            Name = "ST102:0",
            Gateway = "192.168.5.151",
            Path = "",
            PlcType = PlcType.MicroLogix,
            Protocol = Protocol.ab_eip,
            ElementSize = 84,
            ElementCount = 1
        };  //84 = 2 bytes length + 82 chars

        myTag.Initialize(); 

        myTag.Read();

        StringMarshaller str = new StringMarshaller()
        {
            PlcType = PlcType.MicroLogix
        };
        string myResultString = str.DecodeOne(myTag, 0, out int strSize);

//-----------------------------------------------------------------------------

Works great once I changed the following in StringMarsheller.cs, particularly MicroLogixDecode:

//----------------------------------------------------------------------------

    const int MAX_CONTROLLOGIX_STRING_LENGTH = 82; //82 characters max in string
    const int MAX_LOGIXPCCC_STRING_LENGTH = 82;        //82 characters max in string 
    const int MAX_MICRO800_STRING_LENGTH = 80;         //80 characters max in string 

    override public int? ElementSize
    {
        get
        {
            switch (PlcType)
            {
                case PlcType.Plc5: 
                case PlcType.Slc500:
                case PlcType.LogixPccc: 
                case PlcType.MicroLogix: 
                    return 84; //2 bytes length + 82 chars
                case PlcType.ControlLogix: 
                    return 88; //4 bytes length + 82 chars + 2 padding;
                case PlcType.Micro800: 
                    return 82; //2 bytes length + 80 chars
                default: throw new NotImplementedException();
            }
        }
    }

    string MicroLogixDecode(Tag tag, int offset)
    {
        var apparentStringLength = (int)tag.GetInt16(offset);

        var actualStringLength = Math.Min(apparentStringLength, MAX_LOGIXPCCC_STRING_LENGTH);

        var readLength = actualStringLength + (actualStringLength % 2); //read 1 more if odd number

        var asciiEncodedString = new byte[actualStringLength];

        for (int ii = 0; ii < readLength - 1; ii+=2)
        {
            asciiEncodedString[ii] = tag.GetUInt8(offset + 2 + ii + 1);
            if ((apparentStringLength % 2 == 0) || (ii < (actualStringLength - 1))) 
                //don't do for last char in odd number string (i.e. don't get the /0)
            {
                asciiEncodedString[ii + 1] = tag.GetUInt8(offset + 2 + ii);
            }
        }

        return Encoding.ASCII.GetString(asciiEncodedString);
    }

//-----------------------------------------------------------------------------

The fixes in MicroLogixDecode are required otherwise you get errors writing 1 byte past the length of asciiEncodedString. Also I've stepped in 2 bytes and dealt with the odd number strings.

I believe MicroLogixDecode should also work for PLC5 & SLC (also PCCC). I assume they are byte-swapped as well.

The size of a Micro800 string (atomic type SHORT_STRING) I got from reading a Kepware Micro800 driver manual. 256 chars did seem excessive and unlikely.

@kyle-github
Copy link
Member

I will set up the C API to use 82 characters for Micro800, but I thought I saw them return a single byte for the count word. I have not had access to a Micro800 for a long time.

@IanRobo75
Copy link
Author

IanRobo75 commented Aug 23, 2020

Do, you are right. Just opened up a CCW project for a Micro800 and strings are 255 characters, so assumedly the first byte is the length as you thought. Micro800's are terrible anyway, should be avoided.

@kyle-github
Copy link
Member

Thanks for checking. I have not had the "pleasure" of using one in a project.

I remember that at least one PLC type returned strings without any padding. Might have been Micro800?

@IanRobo75
Copy link
Author

SLC500 String:
Just tested strings with an old SLC5/05 we have. They work exactly the same as for MicroLogix, as expected. In fact, I selected a MicroLogix at the plc type to prove it.
I noted however, that SLC's don't have the long Integer type, I'd forgotten that, been a while.
That will be the same for PLC5's as well (i.e. strings the same, and no long integers).

@IanRobo75
Copy link
Author

here is my updated StringPlcmapper.cs file, tested reading & writing strings to/from both a MicroLogix & an SLC500:

using System;
using System.Text;

namespace libplctag.DataTypes
{
public class StringPlcMapper : PlcMapperBase, IPlcMapper, IPlcMapper<string[]>
{

    const int MAX_CONTROLLOGIX_STRING_LENGTH = 82;
    const int MAX_LOGIXPCCC_STRING_LENGTH = 82;

    override public int? ElementSize
    {
        get
        {
            switch (PlcType)
            {
                case PlcType.ControlLogix: return 88;
                case PlcType.Plc5: return 84;
                case PlcType.Slc500: return 84;
                case PlcType.LogixPccc: return 84;
                case PlcType.Micro800: return 256; //To be Confirmed
                case PlcType.MicroLogix: return 84;
                default: throw new NotImplementedException();
            }
        }
    }


    override public string Decode(Tag tag, int offset)
    {
        switch (PlcType)
        {
            case PlcType.Plc5:
            case PlcType.Slc500: 
            case PlcType.LogixPccc: 
            case PlcType.MicroLogix: 
                return LogixPcccDecode(tag, offset);
            case PlcType.Micro800: 
                return Micro800Decode(tag, offset);
            case PlcType.ControlLogix: 
                return ControlLogixDecode(tag, offset);
            default: throw new NotImplementedException();
        }
    }

    override public void Encode(Tag tag, int offset, string value)
    {
        switch (PlcType)
        {
            case PlcType.Plc5:
            case PlcType.Slc500:
            case PlcType.LogixPccc: 
            case PlcType.MicroLogix: 
                LogixPcccEncode(tag, offset, value); break;
            case PlcType.Micro800: 
                Micro800Encode(tag, offset, value); break;
            case PlcType.ControlLogix: 
                ControlLogixEncode(tag, offset, value); break;
            default: break;
        }
    }



    string ControlLogixDecode(Tag tag, int offset)
    {
        var apparentStringLength = tag.GetInt32(offset);

        var actualStringLength = Math.Min(apparentStringLength, MAX_CONTROLLOGIX_STRING_LENGTH);

        var asciiEncodedString = new byte[actualStringLength];
        for (int ii = 0; ii < actualStringLength; ii++)
        {
            asciiEncodedString[ii] = tag.GetUInt8(offset + 4 + 2 + ii);
        }

        return Encoding.ASCII.GetString(asciiEncodedString);
    }

    void ControlLogixEncode(Tag tag, int offset, string value)
    {
        if (value.Length > MAX_CONTROLLOGIX_STRING_LENGTH)
            throw new ArgumentException("String length exceeds maximum for a tag of type STRING");

        var asciiEncodedString = Encoding.ASCII.GetBytes(value);

        tag.SetInt16(offset, Convert.ToInt16(value.Length));

        for (int ii = 0; ii < asciiEncodedString.Length; ii++)
        {
            tag.SetUInt8(offset + 4 + 2 + ii, Convert.ToByte(asciiEncodedString[ii]));
        }
    }


    string Micro800Decode(Tag tag, int offset)
    {
        throw new NotImplementedException();
    }

    void Micro800Encode(Tag tag, int offset, string value)
    {
        throw new NotImplementedException();
    }


    string LogixPcccDecode(Tag tag, int offset)
    {
        var apparentStringLength = (int)tag.GetInt16(offset);

        var actualStringLength = Math.Min(apparentStringLength, MAX_LOGIXPCCC_STRING_LENGTH);

        var readLength = actualStringLength + (actualStringLength % 2); //read 1 more if odd number

        var asciiEncodedString = new byte[actualStringLength];

        for (int ii = 0; ii < readLength - 1; ii+=2)
        {
            asciiEncodedString[ii] = tag.GetUInt8(offset + 2 + ii + 1);
            if ((apparentStringLength % 2 == 0) || (ii < (actualStringLength - 1))) 
                //don't do for last char in odd number string (i.e. don't get the /0)
            {
                asciiEncodedString[ii + 1] = tag.GetUInt8(offset + 2 + ii);
            }
        }

        return Encoding.ASCII.GetString(asciiEncodedString);
    }


    void LogixPcccEncode(Tag tag, int offset, string value)
    {
        if (value.Length > MAX_LOGIXPCCC_STRING_LENGTH)
            throw new ArgumentException("String length exceeds maximum for a tag of type STRING");

        var writeLength = value.Length + (value.Length % 2); //add 1 to write length if odd

        var asciiEncodedString = Encoding.ASCII.GetBytes(value);

        tag.SetInt16(offset, Convert.ToInt16(value.Length));

        for (int ii = 0; ii < (writeLength - 1); ii+=2)
        {
            if ((value.Length % 2 == 0) || (ii < (writeLength - 2)))
            //if odd number string then set penultimate char (1 after string end) as /00
            {
                tag.SetUInt8(offset + 2 + ii, Convert.ToByte(asciiEncodedString[ii + 1]));
            }
            else 
            {
                tag.SetUInt8(offset + 2 + ii, 0x00);
            }
            tag.SetUInt8(offset + 2 + ii + 1, Convert.ToByte(asciiEncodedString[ii]));
        }
    }


}

}

@jkoplo
Copy link
Member

jkoplo commented Sep 2, 2020

Very nice! Do you want to open a PR for this or do you want me to just copy it from here?

Your timing is good, we are very close to releasing a public beta on nuget that's closer to a first 'stable' release.

@IanRobo75
Copy link
Author

IanRobo75 commented Sep 2, 2020 via email

jkoplo added a commit that referenced this issue Sep 2, 2020
@jkoplo jkoplo mentioned this issue Sep 2, 2020
@jkoplo jkoplo linked a pull request Sep 2, 2020 that will close this issue
@jkoplo jkoplo closed this as completed in #83 Sep 3, 2020
jkoplo added a commit that referenced this issue Sep 3, 2020
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

Successfully merging a pull request may close this issue.

4 participants