Rockwell EtherNet/IP CIP Devices

What's The Value Of Speaking The Most Popular Industrial Protocol?

Could you increase the ROI of your software product if you could communicate with Rockwell and Allen-Bradley PLCs and other EtherNet/IP™ CIP™ devices?  What if you could add this capability in just a few hours?

Hard Consulting Corporation is one of only a handful of companies to have created a protocol parser capable of communicating with EtherNet/IP™ CIP™ devices (eg. Rockwell ControlLogix, CompactLogix and MicroLogix Controllers, Allen-Bradley PowerFlex AC Drives, etc.).  This proprietary protocol is commonly used in many industrial control solutions worldwide.  Until now, only expensive and inflexible C-based EtherNet/IP protocol parsers have been commercially available. With cpppo, you can access the development speed, reliability and flexibility of Python 2 or 3 to develop your next Linux, Mac or Windows EtherNet/IP™ CIP™ application.  Whether you are looking for an Open Source library or a Commercially licensed module for your next project, Cpppo may be the PLC connectivity solution you are looking for.

With some simple scripting in shell or Python, you can add valuable capabilities to your control system or software product:

Find all the "enip" protocol EtherNet/IP™ CIP™ devices in your LAN:

$ python -m cpppo.server.enip.client --udp --broadcast --list-identity -a 255.255.255.255

Access data from a "simple" non-routing CIP device (eg. MicroLogix, PowerFlex, ...) using only basic Get Attribute services, with CIP data type conversion:

$ python
>>> from cpppo.server.enip.get_attribute import proxy_simple
>>> product_name, = proxy_simple( "10.0.1.2" ).read( [('@1/1/7','SSTRING')] )
>>> product_name
[u'1756-L61/C LOGIX5561']

Poll EtherNet/IP™ CIP™ devices in the background of your existing Python program:

import logging
import sys
import time
import threading

from cpppo.server.enip import poll
from cpppo.server.enip.get_attribute import proxy_simple as device

hostname                = sys.argv[1] if len( sys.argv ) > 1 else 'localhost'
values                  = {} # { <parameter>: (<timer>, <value>), ... }
poller                  = threading.Thread(
    target=poll.poll, args=(device,), kwargs={
        'address':      (hostname, 44818),
        'cycle':        1.0,
        'timeout':      0.5,
        'process':      lambda par,val: values.update( { par: val } ),
        'params':       [('@1/1/1','INT'),('@1/1/7','SSTRING')],
    })
poller.daemon           = True
poller.start()

# Monitor the values dict (updated in another Thread)
while True:
    while values:
        logging.warning( "%16s == %r", *values.popitem() )
    time.sleep( .1 )

Limited Time Offer

We are offering a combination of Support with 4 hours of developer access, including Commercial Licensing, for a combined price of just USD$1,250.00/yr.  Don't miss out on the opportunity to quickly gain fast and reliable EtherNet/IP CIP support in your product or service!

Create an EtherNet/IP Client, Controller or Simulator

If your Linux device, or Mac or Windows application is network accessible, you can quickly and easily make it accessible to clients which use the EtherNet/IP™ CIP™ protocol; for example, User Interfaces, SCADA systems, and various Allen-Bradley or Rockwell-compatible PLCs.  Alternatively, if you simply need to access an existing EtherNet/IP™ protocol device from within your Linux or Windows application, you can use our cpppo Python module to accomplish that, too.  You can also quickly and easily run one or many EtherNet/IP Controller simulators, either from the command line or a script, or at scale via Linux Docker containers.

Using our cpppo.server.enip Python module, you can provide either EtherNet/IP CIP numeric Class, Instance and Attribute addressing (or assign Tag names) to allow access to various 8/16/32-bit integer and 32-bit real valued scalar and array data within your device, and allow clients to read and write the data using standard EtherNet/IP™ CIP™ protocol.

Using our cpppo.server.enip.client Python module, get access to data from existing EtherNet/IP CIP devices such as ControlLogix PLCs.  Advanced pipelined request I/O processing can obtain extremely high transaction thruput, even over very high latency links (such as Satellite routes).

The following Rockwell EtherNet/IP™ CIP™ requests are supported, in either UDP/IP or TCP/IP protocol (as appropriate):

  • Register Session
  • List Identity
  • List Services
  • List Interfaces
  • Read Tag
  • Read Tag Fragmented
  • Write Tag
  • Write Tag Fragmented
  • Multiple Service Packet
  • Get Attribute Single
  • Set Attribute Single
  • Get Attributes All

Whether you simply need EtherNet/IP™ CIP™ connectivity for in-house use, or you want to go on to produce and compliance-test a fully ODVA™ CONFORMANT EtherNet/IP™ CIP™ device, the Cpppo library is quite likely to be the simplest, quickest and most cost-effective way for you to achieve your goal.

Call us at +1-780-970-8148 today or email us at This email address is being protected from spambots. You need JavaScript enabled to view it. to discuss how you could make your project responsive to EtherNet/IP™ requests, probably with just a few hours of effort.  

The Python Cpppo Package

Our protocol parser is implemented in the Python programming language, and is hosted on Github at https://github.com/pjkundert/cpppo.  Python has rapidly become one of the most popular languages used for implementing communications and automation software.  Properly implemented, Python is a spectacularly robust and productive platform for developing industrial automation components.  The cpppo package is now available for quickly prototyping, testing and finally deploying customized industrial automation communication solutions.

For example, to start up a simulated EtherNet/IP controller on port 44818 of a Mac or Linux host, which serves one Tag named "scada", which is an array of 1000 DINTs (32-bit integers):

$ pip install cpppo  # or, if required: sudo pip install cpppo 
$ python -m cpppo.server.enip -v scada=DINT[1000]

You could now access the simulated controller from any standard EtherNet/IP CIP client.  For example (in another window on your Mac or Linux host):

$ python -m cpppo.server.enip.client --print scada[1]=99
               scada[    1-1    ] <= [99]: 'OK'
$ python -m cpppo.server.enip.client --print scada[0-10] scada[ 0-10 ] == [0, 99, 0, 0, 0, 0, 0, 0, 0, 0, 0]: 'OK'

Accessing Tags on your Rockwell Controller

CompactLogix Tag Setup

Create a Tag in your Controller, to be accessed externally by your Python program with Cpppo.  Here is an example of how to do that in a CompactLogix Controller:

Once complete, you can access the named Tag using either the "python -m cpppo.server.enip.client ..." or "enip_client ..." scripts described above, or by using the Cpppo API described below in your Python program. 

Accessing EtherNet/IP CIP Devices from Python

Reading and writing data via EtherNet/IP from your Python application is very easy.  To read, modify and re-read the above simulated "scada" tag on a host named "controller" (use "localhost" if it's running on the same host), with "pipelining" of 2 outstanding commands to provide high thruput over high-latency links such as satellite:

from __future__ import print_function
from cpppo.server.enip import client
import time host = "controller"
tags = [ "scada[0-10]", "scada[1]=99", "scada[0-10]" ] with client.connector( host=host ) as conn: for index,descr,op,reply,status,value in conn.pipeline( operations=client.parse_operations( tags ), depth=2 ): print( "%s: %20s: %s" % ( time.ctime(), descr, value ))

See cpppo/server/enip/thruput.py and client.py for more details.

Alternatively, access a Tag (eg. "scada") in your EtherNet/IP Controller efficiently using a more traditional method-based API, while still maintaining full "pipelining" of multiple commands in-flight:

...
with client.connector( host=host ) as conn:
    req1 = conn.write( "scada[1-3]", data=[111,222,333] )
    req2 = conn.read( "scada[2]" )
    assert conn.readable( timeout=1.0 ), "Failed to receive reply 1"
    rpy1 = next( conn )
    assert conn.readable( timeout=1.0 ), "Failed to receive reply 2"
    rpy2 = next( conn )

If your target is a simple (non-routing) CIP device (such as an AB MicroLogix or PowerFlex), then it cannot handle the CIP Unconnected Send requests which contain CIP routing paths.  To talk to these simple devices, we must disable this functionality.  Here's an example of how to talk to simple CIP devices, using only the basic CIP "Get Attribute Single" and "Get Attributes All" services (assuming your MicroLogix is at IP address 10.0.0.4):

from __future__ import print_function

import time

from cpppo.server.enip.client import connector
from cpppo.server.enip.get_attribute import attribute_operations

host = "10.0.0.4" # Your MicroLogix IP address
send_path = ''
route_path = False
attributes = [ '@1/1/1', '@1/1/7' ]
timeout = 1.0
depth = 1
multiple = 0

with connector( host=host ) as connection:
   operations = attribute_operations( attributes, send_path=send_path, route_path=route_path )
   for idx,dsc,op,rpy,sts,val in connection.pipeline(
           operations=operations, depth=depth, multiple=multiple, timeout=timeout ):
       print( "%s: %3d: (%-8s) %s == %s" % ( time.ctime(), idx, sts if sts else "OK", dsc, val ))

If you put this into a file called test.py and run it in a shell, it will produce something like:

$ python test.py
Thu Jan  7 08:08:52 2016:   0: (OK      ) Single G_A_S      @0x0001/1/1 == [1, 0]
Thu Jan  7 08:08:52 2016:   1: (OK      ) Single G_A_S      @0x0001/1/7 == [20, 49, 55, 53, 54, 45, 76, 54, 49, 47, 66, 32, 76, 79, 71, 73, 88, 53, 53, 54, 49]

Add EtherNet/IP Protocol Support to Your Product

Adding EtherNet/IP connectivity to an existing application could take minutes.  For example, let's make an EtherNet/IP interface to access any city's current temperature:

#
# cpppo.server.enip.weather
#
# Access a Tag-named location's current temperature via EtherNet/IP.
#
#     $ python -m cpppo.server.enip.weather London=REAL &
#     $ python -m cpppo.server.enip.client --print London
#               London              == [15.319999694824219]: 'OK'
#
from __future__ import absolute_import
from __future__ import print_function
from __future__ import division

__author__                      = "Perry Kundert"
__email__                       = "This email address is being protected from spambots. You need JavaScript enabled to view it."
__copyright__                   = "Copyright (c) 2013 Hard Consulting Corporation"
__license__                     = "Dual License: GPLv3 (or later) and Commercial (see LICENSE)"

import sys, logging, json
try: # Python2
    from urllib2 import urlopen
    from urllib import urlencode
except ImportError: # Python3
    from urllib.request import urlopen
    from urllib.parse import urlencode

from cpppo.server.enip import device, REAL
from cpppo.server.enip.main import main

class Attribute_weather( device.Attribute ):
    OPT                         = {
        "appid": "078b5bd46e99c890482fc1252e9208d5",
        "units": "metric",
        "mode":  "json",
    }
    URI                         = "http://api.openweathermap.org/data/2.5/weather"

    def url( self, **kwds ):
        """Produce a url by joining the class' URI and OPTs with any keyword parameters"""
        return self.URI + "?" + urlencode( dict( self.OPT, **kwds ))

    def __getitem__( self, key ):
        """Obtain the temperature of the city's matching our Attribute's name, convert
        it to an appropriate type; return a value appropriate to the request."""
        try:
            # eg. "http://api.openweathermap.org/...?...&q=City Name"
            data                = urlopen( self.url( q=self.name )).read()
            if type( data ) is not str: # Python3 urlopen.read returns bytes
                data            = data.decode( 'utf-8' )
            weather             = json.loads( data )
            assert weather.get( 'cod' ) == 200 and 'main' in weather, \
                weather.get( 'message', "Unknown error obtaining weather data" )
            cast                = float if isinstance( self.parser, REAL ) else int
            temperature         = cast( weather['main']['temp'] )
        except Exception as exc:
            logging.warning( "Couldn't get temperature for %s via %r: %s",
                             self.name, self.url( q=self.name ), exc )
            raise
        return [ temperature ] if self._validate_key( key ) is slice else temperature

    def __setitem__( self, key, value ):
        raise Exception( "Changing the weather isn't that easy..." )

sys.exit( main( attribute_class=Attribute_weather ))

Lets try it out:

$ python -m cpppo.server.enip.weather London=REAL Calgary=DINT Moscow=SINT Kahului=INT &
[1] 60606
$ python -m cpppo.server.enip.client --print London Calgary Moscow Kahului London == [13.09000015258789]: 'OK' Calgary == [15]: 'OK' Moscow == [15]: 'OK' Kahului == [27]: 'OK'

Licensing

The Cpppo EtherNet/IP CIP protocol parser implementation is available from Hard Consulting Corporation under two licenses; the GNU GPL Version 3 (Open Source) license, and a Commercial (Closed Source) license.  Since we fully developed every aspect of our EtherNet/IP CIP protocol parser in-house, we hold full authority to license the Cpppo library in whatever form we wish.

We strongly support Open Source licensing, because we have benefited greatly from the use of Open Source software.  We wish to support the "maker" community, and enable individuals and corporations to research and develop prototypes rapidly, with inexpensive and developer-friendly Open Source licenses.  However, we understand that your business model may be best served by retaining full privacy and control of the source code of your product, which uses Cpppo as a component.  Therefore, we can provide Cpppo to your company under a safe, growth-friendly Commercial closed-source license, if you so desire.

GNU GPLv3 -- In-house Use

The GNU GPL license allows you to use the software in private scenarios (eg. during development and testing), completely free of charge and without obligation.  So long as you never release access to your software beyond your company's walls, you do not need to obtain a Commercial license, nor do you need to provide a copy of your software source code to anyone outside of your company.

GNU GPLv3 -- External Use

Once you wish to deploy your software based on Cpppo (or any other GPL-licensed library), you must comply with the terms of the GPL.  One of the most important terms is that any software based on GPL software source code must itself be provided with access to its source code (and any changes to any of the GPL source code used by the product).

If you wish, you can continue to freely use the Cpppo library without cost -- as long as you provide your users with access to your software product source code, and any changes made to any GPL libraries.

Commercial License

If you wish to retain your proprietary software source code (or any proprietary changes to Cpppo) privately within your company, then you must obtain a Commercial license to Cpppo.  This provides you with a license authorizing you to retain full privacy of any source code you develop that uses Cpppo.

Our commercial license is very liberal, and is very simple.  It allows your company to safely acquire the capability to develop and deploy software or hardware products that speak EtherNet/IP CIP protocol (but not systems that primarily just repackage and allow configuration of Cpppo), and can be roughly summarised in one sentence:

Hard Consulting Corporation hereby grants to <Your Company> a worldwide, non-revocable, non-exclusive, non-transferable and non-sublicensable Commercial License to use and deploy current and future versions of the Cpppo library, in whatever form they desire.

Support

Should you desire ongoing support for your organization while developing and using Cpppo, you can purchase an annual support contract.  This allows you access to Hard Consulting developers for assistance in developing your application, custom enhancements to Cpppo, etc.  This support is available regardless of the type of licensing you choose for your use of Cpppo.  If you are developing industrial quality systems, it is recommended that you subscribe to the annual support during at least the development period.

LicenseCostPurchase
Cpppo EtherNet/IP Non-exclusive Enterprise-wide Commercial License USD$5,000 This email address is being protected from spambots. You need JavaScript enabled to view it.
USD$4,500
(10% discount)
Cpppo annual support (incl. up to 8 hours of developer access) USD$1,000 This email address is being protected from spambots. You need JavaScript enabled to view it.
USD$900
(10% discount)
Cpppo Annual Support + Commercial License (incl. up to 4 hours of developer access)

Limited Time Offer!
USD$1,500 This email address is being protected from spambots. You need JavaScript enabled to view it.
USD$1,250
(16% discount)