dmroeder / pylogix

Read/Write data from Allen Bradley Compact/Control Logix PLC's
Apache License 2.0
599 stars 182 forks source link

Double read packets? #201

Closed corylutton closed 1 year ago

corylutton commented 2 years ago

First of all thanks for such a great library, we use it quite often.

I've been using wireshark to understand what is being sent for a read request. In doing so, I have run into something that just plain looks odd. When doing a read, it sends what appears to be two requests for read. The chain of events for a connect/read/disconnect looks like this....

  1. EIP Register Req -> EIP Register Resp
  2. CIP Large Forward Open -> CIP Service not supported
  3. CIP Forward Open -> CIP Forward Open Success (super cool it figures this out)
  4. CIP 0x52 -> CIP 0x52 Success
  5. CIP 0x4c -> CIP 0x04c Success
  6. CIP Forward Close -> CIP Forward Close Success
  7. EIP Unregister

The tag in question is a DINT and is setup on an older (v16 I think) Control Logix.

Any light you can shine on why 0x52 followed by 0x4c request/response cycle? The data returned appears to be the same number both times. Seems like there should only be 1 but thought I'd ask as I'm working on a basic set of functions in Go to do similar but more simple read/write. If it matters, I'm on the latest version 0.8.6 and running on Debian Bullseye + Python 3.9

Thanks Cory

dmroeder commented 2 years ago

I'm actually working on some changes with this exactly. I haven't thought about this spot of code in a bit.

The first read, using 0x52 is simply getting the data type. For reads, it is sometimes necessary to know the data type up front, for writes, it is always necessary. Once I know the data type, I save it so I don't have to repeat that every time. If the user provides the data type up front, that initial read to get the data type is skipped.

Some changes I'm pushing out soon will remove the 0x52 service and use the 0x4c service exclusively, though I haven't fully validated the changes. Where things are weird are with boolean arrays or bits of a word. If you read 10 bools in a boolean array, I don't know yet the type, I can't just request a 10 elements, if the type happened to be a DINT and there were only 3, that wouldn't work out. So the initial read to get the data type requests only 1 element to be sure that it will be successful. Once I know that it is a BOOL array, I know that BOOL's are packed in 32 bits, so I may request 1 element and extract the values. But if you want 10 bools starting at MyBool[29], I need to request 2 elements because the values span 2 words.

There are more optimizations that I can do, currently, I went with "simplicity". Treat every new type the same.

Hopefully that makes some sense. Again, I'll be pushing some changes here.

kyle-github commented 2 years ago

I do a similar thing. I always do a first read to get the type and then another if the user asks for it. You need the type if you are going to write. I do a pre-read like this for two reasons: one, I get the data type the first time, and two, if the user turns right around and does a write, I need the data type and the old data if someone wants to write.

However, I thought that 0x4c was for a first read request of a tag. If the tag fits, then you get the response with a zero status (no error). If the tag won't fit into the response packet, then you get a 0x06 status (partial data) and need to use the 0x52 CIP command with an offset to get the rest. Some PLCs only support 0x4C (Omron? Micro800? Can't remember).

corylutton commented 2 years ago

Appreciate the detailed responses, that helps clarify what I'm seeing. I'm only intending to do basic read/write of simple tags where I will know the data type up front so I'll stick with replicating 4c (read) and 4d (write) first as that probably addresses my use case in the immediate term. Perhaps later include partial read and such if needs change.

Hard to find good protocol info as it seems to not be documented very openly from what I can tell. I'll check out Kyle's work too to get some additional protocol detail/ideas.

dmroeder commented 2 years ago

This is the best and most concise documentation I've found:

https://literature.rockwellautomation.com/idc/groups/literature/documents/pm/1756-pm020_-en-p.pdf

You'll only need to implement "partial read" if you plan on reading arrays that are larger than what will fit in the return packet. Using the service that I did to get the data type was a relic from me trying to figure out what I was doing. Going forward, I'll be using 0x4c.

corylutton commented 2 years ago

Not sure why I had not found that, guess I was searching for the wrong thing. That shines a light on things, I was basically reverse engineering using wireshark and code examples for the most part... needless to say much more difficult than having the right documentation. This will save me lots of time. Appreciate your assistance.

kyle-github commented 2 years ago

Don't get too excited :-)

There are a lot of things that these docs do not cover. For instance, when you send a packet with packed requests (CIP type 0x0A) you will get back the same number of responses. But if some of your requests use up all the space in the response you will get some responses with just the minimal 4 byte responses in CIP with a 0x06 status code.

I don't remember if the newer Forward Open is covered in that doc. If it isn't, then it is pretty straightforward to see what is going on from the pylogix code.