ottowayi / pycomm3

A Python Ethernet/IP library for communicating with Allen-Bradley PLCs.
MIT License
404 stars 88 forks source link

Unrouted CIP Messages To PowerFlex 525 Drives #210

Closed JL00001 closed 2 years ago

JL00001 commented 2 years ago

I am using pycomm3 to write parameters for PowerFlex 525 Drives. I based how I am doing this off of your generic messaging examples here: https://docs.pycomm3.dev/en/latest/examples/generic_messaging_examples.html

My question is this: Do I need to use a CIP routing string "drive_path = '10.10.10.100/bp/1/enet/192.168.1.55'" or can I use just use the drive's IP address? I have played around the route_path=True argument but with no luck. I keep getting Generic message 'forward_open' failed: Service not supported and forward_open failed - Service not supported

This is the code I am using:

with pycomm3.CIPDriver("192.168.100.33") as pf525:
        result = pf525.generic_message(
                service=pycomm3.Services.get_attribute_single,
                class_code=b'\x93',
                instance=29,
                attribute=b'\x09',
                data_type=pycomm3.INT,
                connected=True,
                unconnected_send=True,
                route_path=True,
            )
    print(result.value)
JL00001 commented 2 years ago

What's interesting is that the read request seems to go through IE I get data, but I have these two errors messages. I wrote a drive object to store my methods for reading/writing etc, but when I move that segment of code outside of the object; I do not get error messages. I do when they are inside my object

2022-03-07 16:35:00|pycomm3.cip_driver.CIPDriver| ERROR   | Generic message 'forward_open' failed: Service not supported
2022-03-07 16:35:00|pycomm3.cip_driver.CIPDriver| ERROR   | forward_open failed - Service not supported
5001
ottowayi commented 2 years ago

What's causing the issue is connected=True, you'll want that to be False usually. A connected message uses a CIP connection that is created by the forward open service, and and unconnected message uses UCMM to forward messages to the target. Connected messaging has some performance benefits, but for single requests like this you'll not see any of them.. and obviously can't use them if they're not supported.

You'll need the full CIP route to the drive as well. You can put the full path in the for the driver. What happens is the route is split into the IP address and the rest of the CIP path. So for you example above, we'd open a connection to 10.10.10.100 and then store bp/1/enet/192.168.1.55 as the CIP path to the target. So when you send a request, we send it to 10.10.10.100 and then tell it's UCMM to forward it on until it get's to 192.168.1.55. This is what route_path=True does, it puts the remaining CIP path into the message. You can also open a driver with just the IP like CIPDriver('10.10.10.100') and then set route_path='bp/1/enet/192.168.1.55 to get the same effect. That method works well if you have a bunch of drives you want to do at once, you only end up needing to create one driver. This is also when unconnected_send comes in, since you have a route and are using an unconnected message, we need to use a Unconnected Send service to tell the UCMM to forward on our message. So you'll want that to be True, or else our message stops at whatever device is 10.10.10.100. I didn't realize the uncconected send was so simple when I originally wrote this, so whenever I get the next version ready it'll be removed and become automatic.

TL;DR try this:

with pycomm3.CIPDriver("10.10.10.100/bp/1/enet/192.168.1.55") as pf525:
        result = pf525.generic_message(
                service=pycomm3.Services.get_attribute_single,
                class_code=b'\x93',
                instance=29,
                attribute=b'\x09',
                data_type=pycomm3.INT,
                connected=False,
                unconnected_send=True,
                route_path=True,
            )
    print(result.value)
JL00001 commented 2 years ago

So what I am seeing is this; even with the two errors Generic message 'forward_open' and forward_open failed I am getting data from my reads and my writes are going through. I haven't tested this to my fullest satisfaction yet but, other than the error messages; it seems to work without the CIP path, just the drive IP. How is that possible? I will test tomorrow anyway

I should mention that I am on a flat network; No routers or backplanes between me and the drive.

ottowayi commented 2 years ago

Oops, yeah it's supposed to be lowercase, my mistake. I misunderstood your original post, you're seeing the forward open errors in the log because there's two types of forward opens. The extended version is only supported on modern hardware and supports much large message sizes. When connected is True, it will attempt the extended version first and then fallback to the standard. If you post the full log I can verify that it's what you're seeing.

If you're on the same network as the drive then you don't need to do any routing, if you can ping it you can just use the IP. I had assumed you it was in a private network.

JL00001 commented 2 years ago

That would explain this error: 2022-03-08 10:38:20|pycomm3.cip_driver.CIPDriver| ERROR | Generic message 'generic' failed: Too much data

Log:

2022-03-08 10:38:20|pycomm3.cip_driver.CIPDriver| DEBUG   | Opening connection to 192.168.100.33
2022-03-08 10:38:20|pycomm3.cip_driver.CIPDriver| DEBUG   | Sent: RegisterSessionRequestPacket(message=[b'\x01\x00', b'\x00\x00'])
2022-03-08 10:38:20|pycomm3.cip_driver.CIPDriver| DEBUG   | Received: RegisterSessionResponsePacket(session=41, error=None)
2022-03-08 10:38:20|pycomm3.cip_driver.CIPDriver| INFO    | Session=41 has been registered.
2022-03-08 10:38:20|pycomm3.cip_driver.CIPDriver| INFO    | Sending generic message: generic
2022-03-08 10:38:20|pycomm3.cip_driver.CIPDriver| DEBUG   | Sent: GenericUnconnectedRequestPacket(message=[b'\x10', b'\x03 \x93$>0\t', b'1\x00', b'\x00\x00'])
2022-03-08 10:38:20|pycomm3.cip_driver.CIPDriver| DEBUG   | Received: GenericUnconnectedResponsePacket(service=b'\x10', command=b'o\x00', error='Too much data')
2022-03-08 10:38:20|pycomm3.cip_driver.CIPDriver| ERROR   | Generic message 'generic' failed: Too much data
2022-03-08 10:38:20|pycomm3.cip_driver.CIPDriver| DEBUG   | Sent: UnRegisterSessionRequestPacket(message=[])
2022-03-08 10:38:20|pycomm3.cip_driver.CIPDriver| DEBUG   | Received: UnRegisterSessionResponsePacket()
2022-03-08 10:38:20|pycomm3.cip_driver.CIPDriver| INFO    | Session Unregistered
2022-03-08 10:38:20|pycomm3.cip_driver.CIPDriver| DEBUG   | Opening connection to 192.168.100.33
2022-03-08 10:38:20|pycomm3.cip_driver.CIPDriver| DEBUG   | Sent: RegisterSessionRequestPacket(message=[b'\x01\x00', b'\x00\x00'])
2022-03-08 10:38:20|pycomm3.cip_driver.CIPDriver| DEBUG   | Received: RegisterSessionResponsePacket(session=42, error=None)
2022-03-08 10:38:20|pycomm3.cip_driver.CIPDriver| INFO    | Session=42 has been registered.
2022-03-08 10:38:20|pycomm3.cip_driver.CIPDriver| INFO    | Sending generic message: generic
2022-03-08 10:38:20|pycomm3.cip_driver.CIPDriver| DEBUG   | Sent: GenericUnconnectedRequestPacket(message=[b'\x0e', b'\x03 \x93$>0\t', b'', b'\x00\x00'])
2022-03-08 10:38:20|pycomm3.cip_driver.CIPDriver| DEBUG   | Received: GenericUnconnectedResponsePacket(service=b'\x0e', command=b'o\x00', error=None)
2022-03-08 10:38:20|pycomm3.cip_driver.CIPDriver| INFO    | Generic message 'generic' completed
2022-03-08 10:38:20|pycomm3.cip_driver.CIPDriver| DEBUG   | Sent: UnRegisterSessionRequestPacket(message=[])
2022-03-08 10:38:20|pycomm3.cip_driver.CIPDriver| DEBUG   | Received: UnRegisterSessionResponsePacket()
2022-03-08 10:38:20|pycomm3.cip_driver.CIPDriver| INFO    | Session Unregistered

which was created by this code:

def __write_pf525_data(Class_Code, instance, request_data, Attribute):
            with pycomm3.CIPDriver("192.168.100.33") as drive:
                          result = drive.generic_message(
                              service=pycomm3.Services.set_attribute_single,
                              class_code=Class_Code,
                              instance=instance,
                              attribute=Attribute,
                              connected=False,
                              request_data=pycomm3.INT.encode(request_data),
                              )

It doesn't seem to be falling back to the standard forward open as you described

JL00001 commented 2 years ago
2022-03-08 10:49:10|pycomm3.cip_driver.CIPDriver| DEBUG   | Opening connection to 192.168.100.33
2022-03-08 10:49:10|pycomm3.cip_driver.CIPDriver| DEBUG   | Sent: RegisterSessionRequestPacket(message=[b'\x01\x00', b'\x00\x00'])
2022-03-08 10:49:10|pycomm3.cip_driver.CIPDriver| DEBUG   | Received: RegisterSessionResponsePacket(session=43, error=None)
2022-03-08 10:49:10|pycomm3.cip_driver.CIPDriver| INFO    | Session=43 has been registered.
2022-03-08 10:49:10|pycomm3.cip_driver.CIPDriver| INFO    | Sending generic message: generic
2022-03-08 10:49:10|pycomm3.cip_driver.CIPDriver| DEBUG   | Sent: GenericUnconnectedRequestPacket(message=[b'\x0e', b'\x03 \x93$>0\t', b'', b'\x00\x00'])
2022-03-08 10:49:10|pycomm3.cip_driver.CIPDriver| DEBUG   | Received: GenericUnconnectedResponsePacket(service=b'\x0e', command=b'o\x00', error=None)
2022-03-08 10:49:10|pycomm3.cip_driver.CIPDriver| INFO    | Generic message 'generic' completed
2022-03-08 10:49:10|pycomm3.cip_driver.CIPDriver| DEBUG   | Sent: UnRegisterSessionRequestPacket(message=[])
2022-03-08 10:49:10|pycomm3.cip_driver.CIPDriver| DEBUG   | Received: UnRegisterSessionResponsePacket()
2022-03-08 10:49:10|pycomm3.cip_driver.CIPDriver| INFO    | Session Unregistered

A read request doesn't seem to have that same error

JL00001 commented 2 years ago

OK! I figured it out! Not through intelligence, through but stubbornness and a bit of luck;

the source for your CIP Driver at https://docs.pycomm3.dev/en/latest/_modules/pycomm3/cip_driver.html#CIPDriver.generic_message has this line of code:

def generic_message(
        self,
        service: Union[int, bytes],
        class_code: Union[int, bytes],
        instance: Union[int, bytes],
        attribute: Union[int, bytes] = b"",
        request_data: Any = b"",
        data_type: Optional[Union[Type[DataType], DataType]] = None,
        name: str = "generic",
        connected: bool = True,
        unconnected_send: bool = False,
        route_path: Union[bool, Sequence[CIPSegment], bytes, str] = True,
        **kwargs,
    ) -> Tag:

the critical part is

route_path: Union[bool, Sequence[CIPSegment], bytes, str] = True,

So if I don't set route_path = False, it gets set to True. Yea Python 101 stuff Using these Arguments; I am able to do an unrouted CIP Message to my drives with no issues or errors

def write_pf525_data(class_code, instance, request_data, attribute):
            with pycomm3.CIPDriver("192.168.100.33") as drive:
                      result = drive.generic_message(
                                          service=pycomm3.Services.get_attribute_single,
                                          class_code=class_code,
                                          instance=instance,
                                          attribute=attribute,
                                          data_type=DataType,
                                          connected=False,
                                          route_path=False,
                                      )

Why have I banged my head against this for so long? With routed CIP Messages I needed to know the IP address of the PLC and the slot number of the CPU. Using your pycomm3.CIPDriver.discover() that is possible to find but it has been a failure point for some time, as there are numerous combinations of processer types and chassis configurations

@ottowayi I thank you for your time and for creating a wonderful library; and your continuing efforts to support it

ottowayi commented 2 years ago

Ohh yeah, sorry that is my fault, I forgot to mention that detail about route_path. You're not the first person to have the same issue and I totally forgot about it. Right now I'm not very happy with the generic_message implementation, it requires the user to know all the details of CIP messaging and set the correct parameters. I started working on a pretty significant rewrite that includes a refactor of this method to make it a bit simpler and easier to use by making some of those parameters more automatic.

I'm glad you figured it out, but I need to get this documented and the refactor done so others don't run into the same issues.

JL00001 commented 2 years ago

Good sir, I am the LAST person to complain about documentation as my codebase doesn't have any. I too am tormented by imperfect code and constant need to refactor and change what I have already written.

I wish you luck on your rewrite, and if you ever want a laugh, check out my codebase and see how yours could be worse