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

Writing an array of DINTS using client.connector’s write #20

Closed karyuv closed 7 years ago

karyuv commented 8 years ago

Hello Everyone, I have been using this library for a variety of projects and its very useful!!.

I have used client.connector read function in the past to read some tag values on the PLC side and it works great!!. Now I am trying to write an array of dints using client.connector's write function. Any thoughts on that?. Is it possible to share any code snippets?. Thank you.

pjkundert commented 8 years ago

Ya, I've been working on generalizing the cpppo methods to more cleanly handle writes. It works fine (you can try it out using cpppo.server.enip.client via the Command-Line Interface (see docs). Remember to specify the data type as DINT, eg ... 'some_tag[0-3]=(DINT)0,1,2,3'

I have an experimental branch called 'feature-proxy-write' that is a work-in-progress, to get the cpppo.server.enip.get_attribute 'proxy' interface to cleanly handle both reading and writing. You could take a look at that.

I have been much too busy to work on this; some Cpppo "Support" contracts could help that... ;)

karyuv commented 8 years ago

Hello Sir, Thanks a lot for your swift reply. Here is the python code; which tries to write an array of dints.

while True:
        try:
            with client.connector( host= plc_ip_address) as conn:
                while True:
                    time_prev=time.time()
                    data = []
                    for c in range(5):
                        data.append(int(0))

                    req = conn.write("CDS_PLC_RawData_TLC", data,"DINT")
                    try:

                        rpy = next(conn)
                        print rpy

                    except AssertionError:  
                        print "Response timed out!! Tearing Connection and Reconnecting!!!!!"
                        break
                    except AttributeError:
                        print "Tag J1_pos not written:::Will try again::"
                        break

Here is my error:

_Traceback (most recent call last): File "./tag_write.py", line 27, in req = conn.write("CDS_PLC_RawData_TLC", data,"INT") File "/usr/local/lib/python2.7/dist-packages/cpppo-3.9.4-py2.7.egg/cpppo/server/enip/client.py", line 701, in write sender_context=sender_context ) File "/usr/local/lib/python2.7/dist-packages/cpppo-3.9.4-py2.7.egg/cpppo/server/enip/client.py", line 766, in unconnected_send us.request.input = bytearray( device.dialect.produce( us.request )) # eg. logix.Logix File "/usr/local/lib/python2.7/dist-packages/cpppo-3.9.4-py2.7.egg/cpppo/server/enip/logix.py", line 423, in produce result += UINT.produce( data.write_frag.elements ) File "/usr/local/lib/python2.7/dist-packages/cpppo-3.9.4-py2.7.egg/cpppo/server/enip/parser.py", line 172, in produce return struct.pack( cls.structformat, value ) struct.error: cannot convert argument to integer

Any thoughts?. Thank you.

pjkundert commented 8 years ago

The conn.write API is pretty low level, and needs alot of detail information. This is usually provided by client.parse_operations:

>>> from cpppo.server.enip import client
>>> list(client.parse_operations([ "some_tag=(DINT)0,1,2,3"] ))
[{'path': [{'symbolic': 'some_tag'}], 'elements': 4, 'data': [0, 1, 2, 3], 'method': 'write', 'tag_type': 196}]

So, if you want to call it directly, supply:

from cpppo.server.enip import client
client.write( [{'path': [{'symbolic': 'some_tag'}], elements=len( data ), data=data, tag_type=client.enip.DINT.tag_type )
pjkundert commented 8 years ago

Also; don't forget that there is a turn-around time, awaiting the response from the client device.

I've rewritten your example to be a bit more correct:

import socket
import time
from cpppo.server.enip import client
plc_ip_address          = "127.0.0.1"
timeout                 = 5.0

while True:
    try:
        with client.connector( host= plc_ip_address, timeout=timeout ) as conn:
            while True:
                data = [int(0) for c in range( 5 )]
                req = conn.write( "CDS_PLC_RawData_TLC", elements=len( data ), data=data,
                              tag_type=client.enip.DINT.tag_type )
                rpy,ela = client.await( conn, timeout=timeout )
                print rpy

    except AssertionError:
        print "Response timed out!! Tearing Connection and Reconnecting!!!!!"
    except AttributeError:
        print "Tag J1_pos not written:::Will try again::"
    except socket.error as exc:
        print "Couldn't send command: %s" % ( exc )

    time.sleep( .1 )

All this is sort of taken care of, if you use the client.connector API's simple 'process' method. This performs all tag I/O (allows pipeline depth, multiple request packets, etc., if desired), and synchronously returns the list of response after all I/O is complete:

#
# Run simulator in another window:
#     python -m cpppo.server.enip -va localhost --print CDS_PLC_RawData_TLC=DINT[4]
#

import socket
import time
import cpppo
from cpppo.server.enip import client
plc_ip_address                  = "127.0.0.1"
timeout                         = 5.0

import logging
#cpppo.log_cfg['level'] = logging.DETAIL
logging.basicConfig( **cpppo.log_cfg )

tags                            = ["CDS_PLC_RawData_TLC[0-3]=(DINT)0,1,2,3"]
while True:
    try:

        with client.connector( host=plc_ip_address, timeout=timeout ) as conn:
            operations          = client.parse_operations( tags )
            failures,replies    = conn.process(
                operations=operations, timeout=timeout )
        for rpy in replies:
            print rpy

    except Exception as exc:
        print "EtherNet/IP I/O Failed: %s" % ( exc )
    time.sleep( .1 )
karyuv commented 8 years ago

Hello Sir, Thanks a lot for the code!!. It is working well :).

I have one more question; I have been using the client.main(["-a", plc_ip_address, Tag]) to write tags to PLC. I was looking for other write features; as it was not that quick as connector.write. Connector.write is fast because it an unconnected send?.

pjkundert commented 8 years ago

All EtherNet/IP CIP I/O in the Cpppo library use "Unconnected" sends. The key to faster I/O is to set up the connection, and then to use it without closing it. The client.main API always sets up, uses, and then closes a connection. By maintaining your own connection and then using it repeatedly, you get much higher transaction rates.

To improve this much, much further -- use the client.pipeline API. This allows you to issue multiple transaction requests, before receiving the transaction replies. You can keep the PLC "saturated" with requests, instead of letting it sit idle while you await the response to a previous request.