pjkundert / cpppo

Communications Protocol Python Parser and Originator -- EtherNet/IP CIP
https://hardconsulting.com/products/6-cpppo-enip-api
Other
332 stars 109 forks source link

Implementing an Object-Specific Service #27

Closed 12kesselrun closed 7 years ago

12kesselrun commented 7 years ago

So I've been trying to wrap my head around EtherNet/IP over the past week to try to read data from a Keyence DL-EP1 networking adapter. I've read through the cpppo readme multiple times and read quite a few documents about ethernet/ip, but I'm still a little confused about the implementation of the cpppo methods as far as how they interface with a given device. Here's some details of my application:

The DL-EP1 networking adapter takes data from a sensor amplifier and stores it in the following manner:

12.530mm -> Data = 12530 and Number of Decimal Places = 3

I can use poll.py and Get_Attribute_Single (Service code = 0x0E) to read the data = 12530, but to read the number of decimal places I need to use the Service Code = 0x4D at the same Class/Instance/Attribute.

Can I use one of the cpppo methods to specify a Service Code, Class ID, Instance ID, and Attribute ID in order to read this information?

Thanks, Bryan

pjkundert commented 7 years ago

I've been working with EtherNet/IP CIP for years, and I still shake my head in disbelief. So, don't feel bad about how long its taking to understand!

Unfortunately, for "Explicit" CIP, many layers of encapsulation are required to route the request to the Ethernet module, to the right CPU or interface, and then to the right device; only at the lowest level is the Service Code carried.

In Cpppo, these Service Codes (and their accompanying data) are produced and parsed in (for example) the Logix class, in cpppo/server/enip/logix.py. We'd need to create a new 'dialect' class, to pass to cpppo.server.enip.client's 'client' or 'connector' object, to understand the new service codes.

In other words, we'd need probably need to derive a new class from cpppo.server.enip.logix's Logix class. This Logix class understands how to produce and parse the regular ControlLogix "Read Tag" (0x4c), Service Codes. If your Keyence DL-EP1 doesn't do Read/Write Tag, then instead we could derive a class from the more basic Message_Router class or Object class instead -- these only understand how to produce/parse Get/Set Attribute (0x01, 0x0E, 0x10) Service Codes.

Anyway, once we create that class, and know how to produce/parse the payload of this new 0x4D service code -- we would instantiate an instance of cpppo.server.enip.client's 'connector' or 'client':

from cpppo.server.enip import logix, client

class ep1( logix.Logix ):
    ... # produce/parse 0x4D Service Code

with client.connector( host='ep1', dialect=ep1 ) as connection:
   ... # do I/O

I might be able to shake a few hours of consulting time loose to help get this going, if its of value to you...

pjkundert commented 7 years ago

It'll also require amending cpppo.server.enip.client to understand and pass thru the new keyword we designate, which is interpreted in your new ep1 class to issue the 0x4D service code. Its sort of messy right now, as this all grew organically over time...

The 'client' class constructs an elaborate hierarchy of 'dict'-like objects (cpppo.dotdict), containing all of the CIP encapsulation and payload data. At the lowest level are the things like read_tag_fragmented, get_attributes_all, etc. We'll need to teach it a new one, such as get_decimal_places or something, which your new ep1 Message_Router object will be able to interpret.

12kesselrun commented 7 years ago

Should this type of data transfer (reading data from a sensor) use "Implicit" Messaging then? I see that "Implicit" Messaging is useful for transferring data repetitively as opposed to the Request-Then-Receive framework that I've been trying to implement with "Explicit" Messaging.

I'm just trying to figure out if I'm going in the right direction before I dive down the path of implementing a new service code.

Thanks, Bryan

pjkundert commented 7 years ago

Well, that's a possibility -- but you couldn't use Cpppo, then, because we don't implement "Connected" / "Implicit" requests (yet). These are UDP/IP, and use a much simpler encapsulation -- but initiating the "Connected" session is much more complex, and requires the Forward Open request via an Explicit / Unconnected session, which then establishes the Implicit / Connected session...

All this only works within the same LAN, of course, because the receiving party (the EP1 device) has to be able to directly send UDP/IP packets to the initiating party (your Python program). So, they all have to be in the same Layer 2 IP network, usually.