Allow specifying a port in ReceiveMessage
Listening on port 44818 from a PC running RSLinx is a pain. Could we allow specifying a different port to listen in the new ReceiveMessage feature to avoid the conflict? Maybe default it to 44818? ReceiveMessage(ipAddress, return_function, port=44818)
Sounds like a great idea. I'll test this and consider it for the next release. I have a few things I want to add.
Thinking about this more, I don't think you are able to specify the port in the MSG instruction are you? In other words, I think the MSG only sends the payload to 44818.
Are you having an issue with this or are you assuming there will be an issue? I'm running it on a system right now that has RSLinx and is online with the PLC producing the MSG without issue.
Oh you can absolutely specify the port in the MSG instruction. For example "ENET, 2, 10.185.53.51:51000"
I don't know if it's something with my configuration but I find RSLinx binds to port 44818 and my test app can't get that port. If I start my test app first and then RSLinx everything works fine since RSLinx doesn't need the port for normal operations but as soon as I restart my app to change something RSLinx has stolen that port and I have to shut it down and restart Studio5000.
Well there you go. After 25 years of using Rockwell products, still learning new things.
I did not know this either! I will need to support it in my code. I support bridging but I do not support the port number. How is it encoded? Just one big string?
As far as the message.path it's just part of the IP address string, yes.
Good to know. Thanks! I was confused by the length of 22 for a second. I thought it was in hex. Nope. 22 decimal aligns with the path length. Phwew.
yeah there is a trick though which you probably already know: $01 $03 refers to backplane -> slot 3 of course the first $12 seems to identify the start of an IP address string The second $12 (is decimal 18) calls out the length of the IP address string. The catch is, this length has to be an even number. If it's odd, add one to the length by putting a null ($00) character on the end.
At least that's what I do and it has always worked
It is actually a special part of the CIP path elements. 16 means something like "extended path element". Then we add the port to it. port 1=backplane, 2 = first ethernet out port (A), 3 = second ethernet out port (B), 4 = third ethernet out port (A2 apparently),...
16 + 2 = extended path element using port 2.
Yeah, the padding is a fairly common thing in CIP/EIP. But it is not always consistent.
I pushed a branch to accept a port @BenGood if you want to try it out.
I will as soon as I'm back in front of a PLC and not dragged into meetings.... might be next week at this point
Ok I got connected on a custom port today so that's good. I'm just confused about the data that I'm getting, probably unrelated to the port.
my callback function looks like this:
def return_function(return_data):
print(return_data.TagName, return_data.Value, return_data.Status)
when I send a single DINT like this:
I got that DINT the first time, but then on triggering the MSG again I get this array with empty strings inside:
C:\Users\bgood\Documents\GitHub\pylogix [feat/receive-msg-port ≡ +1 ~0 -0 !]> python .\RecieveMsg.py
PLC_TO_SMH -35614123 Success
PLC_TO_SMH ['', '', '', '', -14644155] Success
PLC_TO_SMH ['', '', '', '', 6325789] Success
PLC_TO_SMH ['', '', '', '', 27295730] Success
None None Keyboard Interrupt
This seems to happen almost every time on startup I get a single value the first time (sometimes 0, sometimes 2 times) then I get this array with my data alongside other stuff. Sending the actual UDT that I want to send I get something similar:
C:\Users\bgood\Documents\GitHub\pylogix [feat/receive-msg-port ≡ +1 ~0 -0 !]> python .\RecieveMsg.py
PLC_TO_SMH Success
PLC_TO_SMH Success
PLC_TO_SMH ['', '', '', '', '\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'] Success
PLC_TO_SMH ['', '', '', '', '\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'] Success
Hmm, that is odd, I've never see this on my end. I did all my testing with a CompactLogix. Are you using ControlLogix? I'll see if I can dig one up and test this...
Ahhh my bad. There was another MSG instruction sending other stuff to the same IP:port.
I do have something else though. When I send a UDT with a bunch of values in there, it seems to try to convert them into a utf-8 string somewhere along the line. But not any group of random bytes is valid utf-8. At least thats how I understand this message:
C:\Users\bgood\Documents\GitHub\pylogix [feat/receive-msg-port ≡ +1 ~0 -0 !]> python .\RecieveMsg.py
PLC_TO_SMH Success
None Unknown error 'utf-8' codec can't decode byte 0xd3 in position 16: invalid continuation byte
I expect the problem is coming from here https://github.com/dmroeder/pylogix/blob/14cf88cf79fdb57b6093f083f38398168e176b7f/pylogix/eip.py#L1227
It seems to be trying to find the name of each field but that info isn't there, at least in my case. I was just expecting a bunch of bytes that I'd have to sift through to find the fields of the UDT myself.
I don't expect UDT's to work very well, though I think they should at least return the raw values since that is what pylogix read does. Let me take a look.
The issue is that I was assuming the type was a string, this is where it is failing: https://github.com/dmroeder/pylogix/blob/master/pylogix/eip.py#L1250
I need to check for the struct ID of a STRING type, otherwise, return raw bytes
I'm tied up for a bit, I'll come up with something later this week.
If you can, when you get a chance, try the latest commit to that branch.
yeah that looks like what I was expecting.
Only surprise was that when I sent a longer chunk of data (448 bytes) it seemed to break it into 2 and call my return function twice, once with 392 bytes and then again with another 56 bytes.
Actually, it only happens when the message is set to 'unconnected'. When it's 'connected' it all comes in together.
I assume this is something I don't understand about how the CIP limit of 500 bytes works. I know the limit is sometimes 480 bytes (or 502?). Must be that my 448 bytes somehow exceeds that in unconnected mode.
It would be cool if pylogix could just glue those packets back together and run the callback once though
Unconnected limits you to about 500 bytes (roughly, depends on how you get to the PLC). But that includes various parts of overhead including the CIP response header (4 bytes), some of the CPF header items etc. And then the PLC will not break apart data like DINTs. Either you get the whole DINT or you do not get it at all. With UDTs it gets tricky to see what the PLC might do and where it will break up a response. So it sounds like the PLC may be breaking up the response. When you use a connected message on a newer PLC, you can get up to 4000 bytes.
In general I would always suggest trying to use connected messaging simply for the possible increase in packet size.
The CIP payload for a connected message is 494 bytes. 1 byte for the service, 1 byte for tag name length, the tag name, the remainder of data in the packet is for values. So how much value data can fit in the packet depends on the tag name.
The payload for unconnected seems to be 464 bytes, what I said about the tag name also applies.
What PLC is this? I thought it was a Compact/ControlLogix. If it has firmware that is less than eight or so years old, you should be able to get a connection size of 4002 bytes. Weirdly I think that Micro800 series PLCs can negotiate up to 64k. But they do not support multi-service packets, so you cannot really use it. Thanks, Rockwell.
Unconnected (UCMM) is ~504? I can't remember the exact number. But you need to be really careful with that because if you go through ControlNet, something in the route seems to limit the size to 500 bytes.
I think 504 bytes includes the EIP header. I have a 5380 CompactLogix, while I see the option for Large Connection, it's grayed out and I've never investigated why.
At least in my measurements (reading a very large array of USINT) I found that the CPF data item is count in the 504/4002 bytes but the CPF address item, CPF header and EIP header are not.
Sorry for the long delay @BenGood, if you have time, try the latest commit to the branch.
Thanks @dmroeder! Looks like what I'm making it complicated...
My payload is actually an array of 8 of a UDT with a length of 56. I get the first 7 together and then the 8th comes later. I think the array of UDTs type might cause the message to be a little different and need to be parsed out a little different. Maybe this screenshot will explain what I mean
I added that print statement and self.element_count was 32. I'm out of my depth on figuring out why, I wonder if it assumed 4 bytes per array element
# STRUCT
data = data[4:]
self.element_count = unpack_from("<H", data, 0)[0] * 4
and got 8 * 4 = 32 length?
Alright, I'll make a more complex UDT than I was testing with and figure this out.
Hmm, I think your assessment is spot on. I don't think there's a good way to know the size of the data type. I only know you requested 10 of something, so I don't have a good way to predict how many packets will be coming. The CIP object used tells me there will be multiple packets, but not how many.
A simple solution would be that I include an optional parameter for struct_size. This would only be necessary for reading UDT's since I know the size of the standard types.
If it is not one of the base CI scalar types, and CIP strings, there isn't much you can do without listing out the tag definitions and getting the UDT definitions. Getting the tag definition will get you the dimensions and the size of one element (the size field is interpreted differently for array tags), so the size in bytes of the UDT. But if you want to decode it, you'll need to get the UDT definition.
I thought you were getting the tag listing in order to break up UDTs, @dmroeder ? I am about to embark on that for my library. I need to integrate tag listing into the Omron code in particular because Omron just fails if you request something that does not fit in the response. Omron is easy when you want to write a tag and hard when you want to read. Sigh.