Kepserver CIP messages

239 views
Skip to first unread message

Owen David

unread,
Sep 3, 2020, 2:55:08 AM9/3/20
to libplctag
Hi Kyle,

It is interesting to note the differences between the Kepserver and libplctag handling of the CIP messages.

Using wireshark we can see that kepserver uses <Multiple Service Packet> requests with up to 120 services (tags) requested per packet. The packet size is 1598 bytes. The tag names do not appear in the request but rather it deals with offsets.

An excerpt from the wireshark trace below shows the request sent from Kepserver and the reply from the PLC for one offset or tag.


Part of request message from Kepserver

Service Packet #1: Class (0xac) - Get Attribute List
    Offset: 242
    Common Industrial Protocol
        Service: Get Attribute List (Request)
            0... .... = Request/Response: Request (0x0)
            .000 0011 = Service: Get Attribute List (0x03)
        Request Path Size: 2 words
        Request Path: Class: 0xAC, Instance: 0x01
            Path Segment: 0x20 (8-Bit Class Segment)
                001. .... = Path Segment Type: Logical Segment (1)
                ...0 00.. = Logical Segment Type: Class ID (0)
                .... ..00 = Logical Segment Format: 8-bit Logical Segment (0)
                Class: Unknown (0xac)
            Path Segment: 0x24 (8-Bit Instance Segment)
                001. .... = Path Segment Type: Logical Segment (1)
                ...0 01.. = Logical Segment Type: Instance ID (1)
                .... ..00 = Logical Segment Format: 8-bit Logical Segment (0)
                Instance: 0x01
        Get Attribute List (Request)
            Attribute Count: 2
            Attribute List
                Attribute: 1
                Attribute: 3


Part of reply message sent from PLC

Service Packet #1: Success: Class (0xac) - Get Attribute List
    Offset: 242
    Common Industrial Protocol
        Service: Get Attribute List (Response)
            1... .... = Request/Response: Response (0x1)
            .000 0011 = Service: Get Attribute List (0x03)
        Status: Success: 
            General Status: Success (0x00)
            Additional Status Size: 0 words
        [Request Path Size: 2 words]
        [Request Path: Class: 0xAC, Instance: 0x01]
            [Path Segment: 0x20 (8-Bit Class Segment)]
                [001. .... = Path Segment Type: Logical Segment (1)]
                [...0 00.. = Logical Segment Type: Class ID (0)]
                [.... ..00 = Logical Segment Format: 8-bit Logical Segment (0)]
                [Class: Unknown (0xac)]
            [Path Segment: 0x24 (8-Bit Instance Segment)]
                [001. .... = Path Segment Type: Logical Segment (1)]
                [...0 01.. = Logical Segment Type: Instance ID (1)]
                [.... ..00 = Logical Segment Format: 8-bit Logical Segment (0)]
                [Instance: 0x01]
        Get Attribute List (Response)
            Attribute Count: 2
            Attribute List
                Attribute: 1
                    Attribute Status: Success (0x00)
            Data: aa0003000000604d15bc

======================================


Do you think this approach, using tag offsets instead of tag names, allows for more data to be requested per packet? If so what other benefits could there be? For example does the offset point directly to the PLC memory address whereas supplying a tag name would force the PLC to look up the memory address first?

If this is a more performant approach do you think this is why kepserver would be using it? Could it be added to the library and what benefits are returned for the efforts?

I would be happy to supply more details from my installation if you're interested.

Thanks, Owen




Kyle

unread,
Sep 4, 2020, 9:50:56 AM9/4/20
to libplctag
Hi Owen,

This looks like the symbolic vs. instance tradeoff.   The only method I have found to get the class/instance information for a tag is by scanning the entire PLC.  By scanning the PLC, I mean the process that is done in the list_tags.c example.   There is a lot of information that comes back that is very useful such as array bounds and the base data type etc.   But it is also a lot of overhead since it cannot be done per tag.  You need to scan the whole PLC each time.   And since the library can use packet sizes up to 4002 bytes, I often see 200 or more tags read in a single request.   So the performance of the library looks better (on paper!) than what Kepware is doing.

The huge caveat on all this is if there is a command to get that information for a single tag at a time.   I have not found one, but if your explorations with Wireshark find one, I am very, very interested!   In that case the overhead is per tag and would make some sense to do.   It would allow me to make almost everything except the tag name opusuallytional.  And that would make the library easier to use, which I very much want!

In looking at what Kepware is doing in your sample, it is looking at class 0xAC which is not being translated.   When listing tags, we use class 0xAB.   Is that request done for every tag in the PLC?   Can you get the full dump?   Is there anything that looks like it is getting the information per tag? 

Your example is requesting attributes 1 and 3.   When I list out tags, attribute 1 is the tag name, and that does not look like the data you are getting back.   I do not know what attribute 3 is.  I get 1, 2, 7 and 8 which are the symbol name, the symbol type, the element size and the array dimensions, respectively.  

Could you run the tag listing example against the same PLC with debug set to level 4 and capture the output?   Then see if you can capture the packets from Kepware when you add a new tag to the OPC side?   If would be interesting to see if there is a way to figure out what class 0xAC does!

Thanks for bringing this up.   Perhaps that CIP class is the one that will let me get per-tag information without scanning the whole PLC.

Best,
Kyle

Owen David

unread,
Sep 4, 2020, 10:30:35 AM9/4/20
to Kyle, libplctag
Hi Kyle,

Thanks for your reply.

Yes, sure, I'd be happy to send a longer wireshark capture. See attached.

It is between an AB compactlogix L33ER and Kepserver (latest version) running on a win10 pc.

I believe you can download and run a time limited demo of the Kepserver products. This may be of interest to you?

The capture covers my functioning Kepserver installation being turned on. It seems to do a lot of work before settling into the regular pattern I mentioned in my previous message - asking the PLC for data without giving the tag name.

Be interested to hear your thoughts on this approach and message flow.

Good weekend, Owen




Best,




OWEN DAVID

Chief Engineer, B.Eng



G A L A T E I A

      Wally CENTO,  GEORGETOWN, CI



Email:      Ow...@SYGalateia.com

Cell:        +34 693 712 016

Skype:     owen@sygalateia,com



CONFIDENTIALITY NOTICE:

The information contained in this transmission is intended only for the person or entity to which it is addressed and may contain confidential, trade secret and/or privileged material. If you are not the intended recipient of this information, do not review, re-transmit, disclose, disseminate, use, or take any action in reliance upon, this information. If you received this transmission in error, please contact the sender and destroy all printed copies and delete the material from all computers. The information included in this email does not constitute and will not give rise to any legally binding obligation nor may it be relied upon as the basis for a contract by estoppel or otherwise.



--
You received this message because you are subscribed to the Google Groups "libplctag" group.
To unsubscribe from this group and stop receiving emails from it, send an email to libplctag+...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/libplctag/8a060933-3f5d-49de-9889-733c492e14fdn%40googlegroups.com.
kepserver cip.pcapng.pcapng.gz

Kyle

unread,
Sep 7, 2020, 7:53:41 PM9/7/20
to libplctag
Thanks for the packet dump, Owen!

The first large part of the dump is the Kepware server getting the entire tag listing.  This is what the list_tags.c program does.   This uses "class" 0x6B.  

Then there are a series of calls to class 0x6C which is the class that describes the UDTs.  This is called for all tags that the server found that do not have a known (or base) type.  For each of those there are a series of calls to the template member service (0x4C) on the same class.

For reasons unclear to me, the first part of the dump the Kepware server does a ForwardOpen and only sends small packets.  Then it closes that and does a ForwardOpenEx and starts sending larger packets with multiple CIP commands embedded in them.

Something is trying to scan TAGS.DECK_FUNCTIONS_PDx_FUNCTION over and over but the PLC is responding with an error that indicates that the tag does not exist.

After the first few seconds, the traffic is the same pattern over and over and over and over and over....

Kepware really hammers the PLC with these UDT requests.  I expect it to hit the tag listing once in a while as well, but instead I see just the UDT attributes get read and reread and reread.  

Unfortunately, what I do not see is a way to ask the PLC to translate a symbolic tag name to a numeric ID :-(

This does give me some ideas to try.  

Best,
Kyle

Kyle

unread,
Sep 7, 2020, 8:10:33 PM9/7/20
to libplctag
Ah, wait, I did not translate the call right.  The reason that there are still some 0x52 service call is that those are for the tags that are _not_ in the PLC.  The remaining 0x4c calls on object 0x6B are the tag reads.   I wrote the wrong thing down in my notes as I was scanning through this :-/

The first part, where it scans the tags in the PLC generates the same information as libplctag gets when listing tags.  

Now that I have corrected my error on which command is being sent, I have the question: Are almost all the tags being read top level tags?   I.e. nothing down deep inside a UDT or even an array?

The reason I ask is that the documentation I have says that the ID that we get back from the PLC when enumerating all the tags is only for the first symbolic segment.   So if I have a tag like TAGS.DECK_FUNCTIONS.PB3_FUNCTION, the only part that can be replaced with a direct call to the object is the "TAGS" part.

How often does Kepware rescan the PLC?   If you are programming it and changing tags around, then the map that Kepware gets will become stale.   When I had mistranslated the dump, I thought maybe they were scanning constantly.  As it is, there are many tags (more than 200?) that look like they are being read relatively often.

Best,
Kyle

Kyle

unread,
Sep 10, 2020, 10:56:20 AM9/10/20
to libplctag
Hi Owen,

I had a thought last night that might work here.   I already have a "special" tag "@tags" to get tags from a PLC, perhaps I can use another special syntax to allow use of tags by ID?   The list_tags.c program already pulls back the tag ID.  I do not print it out, but the data is there.  

This gets us most of the way to using the ID form of tags.   There are a few pieces missing:
  1. Determine when to reload tag IDs.   There is an example in the "family bible" from AB, but it is yet another type of CIP call.
  2. Modify the list_tags.c code to print out the ID or perhaps both the symbolic form of the tag string and the ID form.
My goal here is to determine the cost of doing this in the library vs. in user code. 

Why user code?   So that users can make a choice of whether or not to incur the memory cost of this.   A PLC with a few hundred tags will cause the library to use a lot more memory if I do all of this in the library.   One of the long term goals is to have the library fit into very small embedded devices, so having this code and extra data in memory is a problem.

The dotnet wrapper library may provide a path forward.   They have a very low level wrapper and a set of higher level features.   You can use it just like the C version or use those higher level features to save yourself some code at the expense of less control.   I could create another C library, libplctagex for example, that did those higher level things.  Or perhaps it could be turned off with a compile flag?  

There is also the question of increasing the complexity of the library (thus bugs) vs. the performance increase.   The size benefit of moving to ID-based queries is clear but from what I read, there may be a performance benefit as well.

How important to your use cases would such a feature be?  

Best,
Kyle

Jody Koplo

unread,
Sep 10, 2020, 11:34:38 AM9/10/20
to Kyle, libplctag
Kyle-

I'm not sure I'm uptaking everything in the discussion. Essentially, at the low level when you request the value of a tag you ask for it by name currently, but there's a way to ask for it using the 'Tag ID' which is an internal address that PLC can be asked to report back as part of querying all tags in a program? But any changes to the tags on a PLC could potentially change the 'Tag ID' of other tags? Is it basically just the memory address for a tag?

What would querying and using the 'Tag ID' look like in user code? If that's something that _could_ be done now with the existing library then it might be easier to prototype quickly in a higher language to see if there's any performance difference.

Jody

Kyle

unread,
Sep 10, 2020, 4:28:21 PM9/10/20
to libplctag
Hi Jody,

It is a little tricky.   Each "tag" as expressed in the library is just a path to some data.   That path is composed of a top level piece and any number of secondary parts.   A tag like 'myDINTArray[4]' is two pieces.  'myDINTArray' identifies the top level tag (an array) and '[4]' identifies the element in that array.

When you list tags, you get just the top level.   You would get 'myDINTArray' and the array dimensions.   You also get a 16-bit ID (it looks like that is all you can have as the field is 16 bits).   You can replace the symbolic path segment for 'myDINTArray' (which ends up being about 13-14 bytes) with a instance ID segment (not sure what the AB name is) that is about 6 or 7 bytes.   For short tag names, it is a wash.   However you still need the remaining segments.  

Suppose I have a tag like 'myLong.ohSoLong.reallyTrulyLong.tagName'.   Only 'myLong' gets replaced.

So benefit number 1 is that for long tag names, you can save space.  In turn that means that you can pack more tag requests into one packet to the PLC.   That is very good for latency.   The second benefit may be performance.   It is not clear, but from some things I found over the last couple of days there may be a performance benefit of having the first segment of the tag path be an ID.   That is apparently more of an array index or offset or something direct.   Otherwise the PLC has to look up the tag name.  Both of these mean increased performance.   If you have a lot of long tag names and your namespace is fairly flat, then it could be a big win.

You have to keep the tag IDs up to date.  I have found no documentation that suggests that there is a way to take a tag name and do a look up in the PLC to get the ID of the top segment.   The only way documented anywhere is to list out all the tags in the PLC, store them in your own data structure and do the lookup yourself.   Fun.   I can make that fast, but I cannot make that take zero memory.   And then you get the problem that if you upload a program to the PLC or even add (and edit?) tags in RUN/Program mode, the tag IDs might change.   I strongly suspect that existing tags would only change during upload, but that is not something I want to rely on.   So you need to periodically check.

That check can be done in one of two ways.   You can just enumerate all the tags every few minutes.  The problem is that this is a huge amount of data if there are a lot of tags in the PLC.  While you are doing that, you are not reading tags since you are saturating the connection.  The second way is to do a special CIP call on a specific object instance and get back some values.  If those values change (AB does not explain what they are), then you need to reread.

Clearly the only sane way to do this is to do the special CIP call.

It would be fairly easy to extend the library slightly to allow you to input tag names using the ID.  You can get the IDs from the tag listing.   But there is no simple way to get the special CIP data.   I could add another special tag like '@tags' to get the special data.   Then most of this could be done in the user program.

But should it?   I already spend a lot of code to negotiate large packets and pack requests to ControlLogix and CompactLogix PLCs and the performance increase is huge.   So there is precedent for this.   Next, this is extremely PLC specific.   The whole point of library is to abstract away the differences between PLCs as much as possible.

And that is where I am stuck.  I really want to make sure that the library works well for embedded use and that means keeping tight control on memory usage.   Perhaps I could just make this a build flag.  Or maybe build two versions of the library when it is built?  At the same time I want everyone who uses AB PLCs to benefit from higher performance and lower load on the PLC.   Without having to write a lot of boilerplate code.

Best,
Kyle
Reply all
Reply to author
Forward
0 new messages