Read/write values using Ethernet/IP
Asked Answered
V

2

7

I recently have acquired an ACS Linear Actuator (Tolomatic Stepper) that I am attempting to send data to from a Python application. The device itself communicates using Ethernet/IP protocol.

I have installed the library cpppo via pip. When I issue a command in an attempt to read status of the device, I get None back. Examining the communication with Wireshark, I see that it appears like it is proceeding correctly however I notice a response from the device indicating: Service not supported.

Example of the code I am using to test reading an "Input Assembly":

from cpppo.server.enip import client

HOST = "192.168.1.100"
TAGS = ["@4/100/3"]

with client.connector(host=HOST) as conn:
    for index, descr, op, reply, status, value in conn.synchronous(
            operations=client.parse_operations(TAGS)):
        print(": %20s: %s" % (descr, value))

I am expecting to get a "input assembly" read but it does not appear to be working that way. I imagine that I am missing something as this is the first time I have attempted Ethernet/IP communication.

I am not sure how to proceed or what I am missing about Ethernet/IP that may make this work correctly.

Violence answered 16/7, 2015 at 17:43 Comment(0)
P
14

clutton -- I'm the author of the cpppo module.

Sorry for the delayed response. We only recently implemented the ability to communicate with simple (non-routing) CIP devices. The ControlLogix/CompactLogix controllers implement an expanded set of EtherNet/IP CIP capability, something that most simple CIP devices do not. Furthermore, they typically also do not implement the *Logix "Read Tag" request; you have to struggle by with the basic "Get Attribute Single/All" requests -- which just return raw, 8-bit data. It is up to you to turn that back into a CIP REAL, INT, DINT, etc.

In order to communicate with your linear actuator, you will need to disable these enhanced encapsulations, and use "Get Attribute Single" requests. This is done by specifying an empty route_path=[] and send_path='', when you parse your operations, and to use cpppo.server.enip.getattr's attribute_operations (instead of cpppo.server.enip.client's parse_operations):

from cpppo.server.enip import client
from cpppo.server.enip.getattr import attribute_operations

HOST = "192.168.1.100"
TAGS = ["@4/100/3"]

with client.connector(host=HOST) as conn:
    for index, descr, op, reply, status, value in conn.synchronous(
        operations=attribute_operations(
            TAGS, route_path=[], send_path='' )):
        print(": %20s: %s" % (descr, value))

That should do the trick!

We are in the process of rolling out a major update to the cpppo module, so clone the https://github.com/pjkundert/cpppo.git Git repo, and checkout the feature-list-identity branch, to get early access to much better APIs for accessing raw data from these simple devices, for testing. You'll be able to use cpppo to convert the raw data into CIP REALs, instead of having to do it yourself...

...

With Cpppo >= 3.9.0, you can now use much more powerful cpppo.server.enip.get_attribute 'proxy' and 'proxy_simple' interfaces to routing CIP devices (eg. ControlLogix, Compactlogix), and non-routing "simple" CIP devices (eg. MicroLogix, PowerFlex, etc.):

$ 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']

If you want regular updates, use cpppo.server.enip.poll:

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
params                  = [('@1/1/1','INT'),('@1/1/7','SSTRING')]

# If you have an A-B PowerFlex, try:
# from cpppo.server.enip.ab import powerflex_750_series as device
# parms                 = [ "Motor Velocity", "Output Current" ]

hostname                = '10.0.1.2'
values                  = {} # { <parameter>: <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':       params,
    })
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 )

And, Voila! You now have regularly updating parameter names and values in your 'values' dict. See the examples in cpppo/server/enip/poll_example*.py for further details, such as how to report failures, control exponential back-off of connection retries, etc.

Version 3.9.5 has recently been released, which has support for writing to CIP Tags and Attributes, using the cpppo.server.enip.get_attribute proxy and proxy_simple APIs. See cpppo/server/enip/poll_example_many_with_write.py

Puree answered 29/1, 2016 at 23:52 Comment(3)
Is it possible to use proxy_simple to write single attribute?Undertone
@Undertone I came here looking for the same answer. It seems that no, proxy_simple (and proxy) throw a not implemented exception. However, I successfully got attribute writes working using connector and attribute_operations as in the first example. It seems that writes are limited to USINT data type, from what I can tell.Wagner
Apologies, correction to my comment above. Writes (sets) work using UINT as a data type. It was Reads (gets) that I was having trouble with when using UINT.Wagner
T
-2

hope this is obvious, but accessing HOST = "192.168.1.100" will only be possible from a system located on the subnet 192.168.1.*

Tupiguarani answered 16/7, 2015 at 18:19 Comment(1)
Communication to the device is happening correctly using that address. I setup a testing environment specifically for this purpose with the 'correct' addresses.Violence

© 2022 - 2024 — McMap. All rights reserved.