def scan_message_router(tag_name, position, values, types): success_service = set() # Read Tag 0x4c read_tag = [tag_name + '[' + str(position) + ']'] with client.connector(host=PLC_HOST) as conn: for index, descr, op, reply, status, value in conn.pipeline( operations=client.parse_operations(read_tag), depth=2): pass if status == 0x00: success_service.add(0x4c) # Write Tag 0x4d write_tag = [ str(tag_name + '[' + str(position) + ']=' + "(" + types + ")" + str(values)) ] with client.connector(host=PLC_HOST) as conn: for index, descr, op, reply, status, value in conn.pipeline( operations=client.parse_operations(write_tag), depth=2): pass if status == 0x00: success_service.add(0x4d) print(("Class Message Router 0x02 supports specifics serives " + str(success_service))) return success_service
def PLC_Connection(host,tags): retVal=[] with client.connector(host=host) as conn: for index,descr,op,reply,status,value in conn.pipeline( operations=client.parse_operations(tags),depth=2): retVal.append(value) return retVal
def attribute_operations( paths ): for op in client.parse_operations( paths ): if 'attribute' in op['path'][-1]: op['method'] = 'get_attribute_single' else: op['method'] = 'get_attributes_all' yield op
def test_hart_pass_thru_simulated(simulated_hart_gateway): """Simulated HART I/O card; always returns Pass-thru Init handle 99 (won't work on a real device)""" command, address = simulated_hart_gateway #address = ('127.0.0.1',44818) # If you run: python3 ./hart_test.py try: assert address, "Unable to detect HART EtherNet/IP CIP Gateway IP address" hio = client.connector(host=address[0], port=address[1]) operations = [ { "method": "service_code", "code": HART.PT_INI_REQ, "data": [1, 0], # HART: Read primary variable "data_size": 4 + 2, # Known response size: command,status,<payload> "path": '@0x%X/8' % (HART.class_id), # Instance 1-8 ==> HART Channel 0-7 }, { "method": "service_code", "code": HART.PT_QRY_REQ, "data": [99], # HART: Pass-thru Query handle "data_size": 4 + 5, # Known response size: 5 (units + 4-byte real in network order) "path": '@0x%X/8' % (HART.class_id), # Instance 1-8 ==> HART Channel 0-7 }, ] # Now, use the underlying client.connector to issue a HART "Read Dynamic Variable" Service Code cmdbuf = '' with hio: results = [] failures = 0 for idx, dsc, req, rpy, sts, val in hio.pipeline( operations=client.parse_operations(operations), **hart_kwds): log.normal("Client %s: %s --> %r: %s", hio, dsc, val, enip.enip_format(rpy)) if not val: log.warning( "Client %s harvested %d/%d results; failed request: %s", hio, len(results), len(operations), rpy) failures += 1 results.append((dsc, val, rpy)) #cmdbuf = command_logging( command, cmdbuf ) # assert failures == 0 # statuses represent HART I/O status, not CIP response status assert results[0][-1].init.status in ( 32, 33, 35) # 32 busy, 33 initiated, 35 device offline assert results[1][-1].query.status in ( 0, 34, 35) # 0 success, 34 running, 35 dead except Exception as exc: log.warning("Test terminated with exception: %s", exc) raise
def write_xv(): host = "192.168.0.22" #tags = [ "X_Test[0-9]", "X_Test[5]=(DINT)555", "X_Test[0-9]" ] tags = ["X_Test[2]=(DINT)65"] #data = [123] 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 ))
def attribute_operations(self, paths, int_type=None, **kwds): for op in client.parse_operations(paths, int_type=int_type or 'SINT', **kwds): path_end = op['path'][-1] if 'instance' in path_end: op['method'] = 'get_attributes_all' assert 'data' not in op, "All Attributes cannot be operated on using Set Attribute services" elif 'symbolic' in path_end or 'attribute' in path_end or 'element': op['method'] = 'set_attribute_single' if 'data' in op else 'get_attribute_single' else: raise AssertionError("Path invalid for Attribute services: %r", op['path']) yield op
def attribute_operations(paths, int_type=None, **kwds): for op in client.parse_operations(paths, int_type=int_type or "SINT", **kwds): path_end = op["path"][-1] if "instance" in path_end: op["method"] = "get_attributes_all" assert ( "data" not in op ), "All Attributes cannot be operated on using Set Attribute services" elif "symbolic" in path_end or "attribute" in path_end or "element": op["method"] = ("set_attribute_single" if "data" in op else "get_attribute_single") else: raise AssertionError("Path invalid for Attribute services: %r", op["path"]) yield op
def TagData(mode, tag_name, plc_addr): #Building a function with client.connector(host=plc_addr, port=44818, timeout=timeout) as conn: #Creates a UDP connection operations = client.parse_operations([tag_name]) if (mode == "wr"): failures, transactions = conn.process(operations=operations, depth=2, multiple=0, fragment=False, printing=False, timeout=timeout) #Write Tag elif (mode == "rd"): for index, descr, op, reply, status, value in conn.pipeline( operations=operations, depth=2): #Read tag poz = (value[0]) if value is None: print("None returned while reading %s from PLC %s " % (tag_name, plc_addr)) return poz
""" Example of using cpppo.server.enip EtherNet/IP CIP client API. To see the Tag operations succeed, fire up: python -m cpppo.server.enip Tag=DINT[10] """ import sys, logging import cpppo from cpppo.server.enip import (address, client) if __name__ == "__main__": logging.basicConfig( **cpppo.log_cfg ) #logging.getLogger().setLevel(logging.INFO) host = 'localhost' # Controller IP address port = address[1] # default is port 44818 depth = 1 # Allow 1 transaction in-flight multiple = 0 # Don't use Multiple Service Packet fragment = False # Don't force Read/Write Tag Fragmented timeout = 1.0 # Any PLC I/O fails if it takes > 1s printing = True # Print a summary of I/O tags = ["Tag[0-9]+16=(DINT)4,5,6,7,8,9", "@0x2/1/1", "Tag[3-5]"] with client.connector( host=host, port=port, timeout=timeout ) as connection: operations = client.parse_operations( tags ) failures,transactions = connection.process( operations=operations, depth=depth, multiple=multiple, fragment=fragment, printing=printing, timeout=timeout ) sys.exit( 1 if failures else 0 )
from cpppo.server.enip import client if __name__ == "__main__": ap = argparse.ArgumentParser() ap.add_argument( '-d', '--depth', default=0, help="Pipelining depth" ) ap.add_argument( '-m', '--multiple', default=0, help="Multiple Service Packet size limit" ) ap.add_argument( '-r', '--repeat', default=1, help="Repeat requests this many times" ) ap.add_argument( '-a', '--address', default='localhost', help="Hostname of target Controller" ) ap.add_argument( '-t', '--timeout', default=None, help="I/O timeout seconds (default: None)" ) ap.add_argument( 'tags', nargs='+', help="Tags to read/write" ) args = ap.parse_args() depth = int( args.depth ) multiple = int( args.multiple ) repeat = int( args.repeat ) operations = client.parse_operations( args.tags * repeat ) timeout = None if args.timeout is not None: timeout = float( args.timeout ) with client.connector( host=args.address, timeout=timeout ) as conn: start = cpppo.timer() num,idx = -1,-1 for num,(idx,dsc,op,rpy,sts,val) in enumerate( conn.pipeline( operations=operations, depth=depth, multiple=multiple, timeout=timeout )): print( "%s: %3d: %s" % ( timestamp(), idx, val )) elapsed = cpppo.timer() - start print( "%3d operations using %3d requests in %7.2fs at pipeline depth %2s; %5.1f TPS" % ( num+1, idx+1, elapsed, args.depth, num / elapsed ))
""" Example of using cpppo.server.enip EtherNet/IP CIP client API. To see the Tag operations succeed, fire up: python -m cpppo.server.enip Tag=DINT[10] """ import sys, logging import cpppo from cpppo.server.enip import address, client if __name__ == "__main__": logging.basicConfig( **cpppo.log_cfg ) #logging.getLogger().setLevel(logging.INFO) host = 'localhost' # Controller IP address port = address[1] # default is port 44818 depth = 1 # Allow 1 transaction in-flight multiple = 0 # Don't use Multiple Service Packet fragment = False # Don't force Read/Write Tag Fragmented timeout = 1.0 # Any PLC I/O fails if it takes > 1s printing = True # Print a summary of I/O tags = ["Tag[0-9]+16=(DINT)4,5,6,7,8,9", "@0x2/1/1", "Tag[3-5]"] with client.connector( host=host, port=port, timeout=timeout ) as connection: operations = client.parse_operations( tags ) failures,transactions = connection.process( operations=operations, depth=depth, multiple=multiple, fragment=fragment, printing=printing, timeout=timeout ) sys.exit( 1 if failures else 0 )
help="Repeat requests this many times") ap.add_argument('-a', '--address', default='localhost', help="Hostname of target Controller") ap.add_argument('-t', '--timeout', default=None, help="I/O timeout seconds (default: None)") ap.add_argument('tags', nargs='+', help="Tags to read/write") args = ap.parse_args() depth = int(args.depth) multiple = int(args.multiple) repeat = int(args.repeat) operations = client.parse_operations(args.tags * repeat) timeout = None if args.timeout is not None: timeout = float(args.timeout) with client.connector(host=args.address, timeout=timeout) as conn: start = cpppo.timer() num, idx = -1, -1 for num, (idx, dsc, op, rpy, sts, val) in enumerate( conn.pipeline(operations=operations, depth=depth, multiple=multiple, timeout=timeout)): print("%s: %3d: %s" % (timestamp(), idx, val)) elapsed = cpppo.timer() - start
def test_hart_pass_thru_poll(simulated_hart_gateway): r"""To test a remote C*Logix w/ a HART card, set up a remote port forward from another host in the same LAN. Here's a windows example, using putty. This windows machine (at 100.100.102.1) forwards a port 44818 on fat2.kundert.ca, to the PLC at 100.100.102.10:44818: C:\Users\Engineer\Desktop\putty.exe -R 44818:100.100.102.10:44818 [email protected] Now, from another host that can see fat2.kundert.ca: $ python -m cpppo.server.enip.list_services --list-identity -a fat2.kundert.ca:44818 { "peer": [ "fat2.kundert.ca", 44818 ], ... "enip.status": 0, "enip.CIP.list_services.CPF.count": 1, "enip.CIP.list_services.CPF.item[0].communications_service.capability": 288, "enip.CIP.list_services.CPF.item[0].communications_service.service_name": "Communications", } { ... "enip.status": 0, "enip.CIP.list_identity.CPF.item[0].identity_object.sin_addr": "100.100.102.10", "enip.CIP.list_identity.CPF.item[0].identity_object.status_word": 96, "enip.CIP.list_identity.CPF.item[0].identity_object.vendor_id": 1, "enip.CIP.list_identity.CPF.item[0].identity_object.product_name": "1756-EN2T/D", "enip.CIP.list_identity.CPF.item[0].identity_object.sin_port": 44818, "enip.CIP.list_identity.CPF.item[0].identity_object.state": 3, "enip.CIP.list_identity.CPF.item[0].identity_object.version": 1, "enip.CIP.list_identity.CPF.item[0].identity_object.device_type": 12, "enip.CIP.list_identity.CPF.item[0].identity_object.sin_family": 2, "enip.CIP.list_identity.CPF.item[0].identity_object.serial_number": 11866067, "enip.CIP.list_identity.CPF.item[0].identity_object.product_code": 166, "enip.CIP.list_identity.CPF.item[0].identity_object.product_revision": 1802, } """ command, address = simulated_hart_gateway # For testing, we'll hit a specific device #address = ("fat2.kundert.ca", 44818) #address = ("100.100.102.10", 44818) #address = ("localhost", 44818) route_path = None route_path = [{'link': 2, 'port': 1}] try: assert address, "Unable to detect HART EtherNet/IP CIP Gateway IP address" #hio = client.implicit( host=address[0], port=address[1] ) hio = client.connector(host=address[0], port=address[1]) # Just get the primary variable, to see if the HART device is there. operations = [ { "method": "service_code", "code": HART.RD_VAR_REQ, "data": [], # No payload "data_size": 4 + 36, # Known response size: command,status,<payload> "path": '@0x%X/8' % (HART.class_id), # Instance 1-8 ==> HART Channel 0-7 "route_path": route_path, }, ] with hio: for idx, dsc, req, rpy, sts, val in hio.pipeline( operations=client.parse_operations(operations), **hart_kwds): log.normal("Client %s: %s --> %r: %s", hio, dsc, val, enip.enip_format(rpy)) path = '@0x%X/8' % (HART.class_id) data = hart_pass_thru(hio, path=path, hart_data=[1, 0], route_path=route_path, data_size=4) # with no size # The small response carries the 4-byte value, the long CIP MSG response additionally # carries the data type We receive the long response. value = None if data and len(data) >= 4: units = data[0] if len(data) > 4 else None packer = struct.Struct(enip.REAL_network.struct_format) value, = packer.unpack_from(buffer=bytearray(data[-4:])) log.normal("Read primary variable Value: %s (units: %s), from: %r", value, units, data) # HART Command 3 gets all 4 variables data = hart_pass_thru(hio, path=path, hart_data=[3, 0], route_path=route_path, data_size=4 * 4) # with no size # small response carries PV, SV, TV, FV values, no data types value = [] if data and len(data) == 4 * 4: # Short packer = struct.Struct(enip.REAL_network.struct_format) for i in range(0, len(data), 4): value += packer.unpack_from(buffer=bytearray(data[i:i + 4])) elif data and len(data) >= 24: # Long packer = struct.Struct(enip.REAL_network.struct_format) value = cpppo.dotdict() value.current, = packer.unpack_from(buffer=bytearray(data[0:])) value.PV_units = data[4] value.PV, = packer.unpack_from(buffer=bytearray(data[5:])) value.SV_units = data[10] value.SV, = packer.unpack_from(buffer=bytearray(data[11:])) value.TV_units = data[14] value.TV, = packer.unpack_from(buffer=bytearray(data[15:])) value.FV_units = data[19] value.FV, = packer.unpack_from(buffer=bytearray(data[20:])) log.normal("Read all variables Values: %s, from: %r", enip.enip_format(value), data) # HART Command 12 gets the 24-character Message data = hart_pass_thru(hio, path=path, hart_data=[12, 0], route_path=route_path, data_size=4 * 4) # with no size value = None if data and len(data): try: value = bytes(data).decode('ascii') except: value = hexdump(data) log.normal("Read Message: \n%s\nfrom: %r", value, data) # HART Command 0 gets the identity data = hart_pass_thru(hio, path=path, hart_data=[0, 0], route_path=route_path, data_size=4 * 4) # with no size value = None if data and len(data): value = hexdump(data) log.normal("Read Identity: \n%s\nfrom: %r", value, data) # HART Command 13 gets the Tag data = hart_pass_thru(hio, path=path, hart_data=[13, 0], route_path=route_path, data_size=4 * 4) # with no size value = None if data and len(data): value = hexdump(data) log.normal("Read Tag: \n%s\nfrom: %r", value, data) except Exception as exc: log.warning("Test terminated with exception: %s", exc) raise
def operate(host='localhost', port=44818, tags=[], udp=False, broadcast=False, timeout=5, repeat=1, depth=1, fragment=False, route_path=None, send_path='', simple=False, multiple=False, priority_time_tick=5, timeout_ticks=157): """ Read/write specified EthernetIP tags Function arguments are similar to cpppo.server.enip.client command line arguments. Args: host: host to connect (default: localhost) port: port to connect (44818) tags: list of tag operations, e.g. TAG1, TAG2[0-2], TAG3[5]=5, TAG4=77.99 (refer to client CLI for more help) udp: use UDP/IP (default: False) broadcast: allow multiple peers, and use of broadcast address (default: False) timeout: EIP timeout (default: 5s) repeat: times to repeat request (default: 1) depth: pipeline requests to this depth (default: 1) fragment: always use read/write tag fragmented requests (default: False) route_path: <port>/<link> or JSON (default: '[{"port": 1, "link": 0}]'); 0/false to specify no/empty route_path send_path: send Path to UCMM (default: @6/1); specify an empty string '' for no send path simple: access a simple (non-routing) EIP CIP device (eg. MicroLogix, default: False) multiple: use multiple service packet request targeting ~500 bytes (default: False) priority_time_tick: timeout tick length N range (0,15) (default: 5 == 32), where each tick is 2**N ms. Eg. 0 ==> 1ms., 5 ==> 32ms., 15 ==> 32768ms timeout_ticks: timeout duration ticks in range (1,255) (default: 157 == 5024ms) Returns: tuple (result, failures) where result is a list of operation results (lists for get, True for set) and failures is a number of failed operations Raises: socket exceptions if connection has been failed """ addr = (host, port) multiple = 500 if multiple else 0 # route_path may be None/0/False/'[]', send_path may be None/''/'@2/1'. # simple designates '[]', '' respectively, appropriate for non-routing CIP # devices, eg. MicroLogix, PowerFlex, ... route_path = device.parse_route_path( route_path ) if route_path \ else [] if simple else None send_path = send_path if send_path \ else '' if simple else None failures = 0 transactions = [] with connector(host=addr[0], port=addr[1], timeout=timeout, udp=udp, broadcast=broadcast) as connection: if tags: operations = parse_operations( recycle(tags, times=repeat), route_path=route_path, send_path=send_path, timeout_ticks=timeout_ticks, priority_time_tick=priority_time_tick) failed, transactions = connection.process(operations=operations, depth=depth, multiple=multiple, fragment=fragment, printing=False, timeout=timeout) failures += failed return transactions, failures
def test_hart_simple(simulated_hart_gateway): # No Multiple Service Packet supported by HART I/O Card simulator command, address = simulated_hart_gateway #address = ("127.0.0.1", 44818) #address = ("100.100.102.10", 44818) route_path = None route_path = [{'link': 2, 'port': 1}] try: assert address, "Unable to detect HART EtherNet/IP CIP Gateway IP address" hio = client.connector(host=address[0], port=address[1]) # Establish an Implicit EtherNet/IP CIP connection using Forward Open #hio = client.implicit( host=address[0], port=address[1], connection_path=None ) PV = 1.23 operations = [ { "method": "service_code", "code": HART.RD_VAR_REQ, "data": [], # No payload "data_size": 4 + 36, # Known response size: command,status,<payload> "path": '@0x%X/8' % (HART.class_id), # Instance 1-8 ==> HART Channel 0-7 }, 'HART_7_Data.PV = (REAL)0', # would fail 'til first HART Read Dynamic Variable is done { "method": "service_code", "code": HART.RD_VAR_REQ, "data": [], # No payload "data_size": 4 + 36, # Known response size: command,status,<payload> "path": '@0x%X/8' % (HART.class_id), # Instance 1-8 ==> HART Channel 0-7 }, 'HART_7_Data.PV = (REAL)%s' % PV, { "method": "service_code", "code": HART.RD_VAR_REQ, "data": [], # No payload "data_size": 4 + 36, # Known response size: command,status,<payload> "path": '@0x%X/8' % (HART.class_id), # Instance 1-8 ==> HART Channel 0-7 }, ] # Now, use the underlying client.connector to issue a HART "Read Dynamic Variable" Service Code simout = '' with hio: results = [] failures = 0 for idx, dsc, req, rpy, sts, val in hio.pipeline( operations=client.parse_operations(operations, route_path=route_path), **hart_kwds): log.normal("Client %s: %s --> %r: %s", hio, dsc, val, enip.enip_format(rpy)) if not val: log.warning( "Client %s harvested %d/%d results; failed request: %s", hio, len(results), len(operations), rpy) failures += 1 results.append((dsc, val, rpy)) rpylast = results[-1][-1] assert failures in (0, 1) assert near(rpylast.read_var.PV, PV) except Exception as exc: log.warning("Test terminated with exception: %s", exc) raise
#!/usr/bin/env python2 from cpppo.server.enip import client import time host = "192.168.179.131" tags = ["SCADA[1]", "SCADA[2]"] 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))
#host = "192.168.0.22" #tags = "X_Test[5-7]" print("Start Reading PLC") # Read Items from Access Config file into List and strip /n with open('config.txt') as f: w = [word.strip() for word in f] l1 = str(w[1]) #print(l1) #PLC Ip Address: l3 = str(w[3]) #print(l3) #PLC Tags: host = (l1) tags = [l3] with client.connector( host=host ) as conn_w: req1 = conn_w.write('X_Text[0-9]', data=[1,1,1,1,1,1,1,1,1,1]) req2 = conn_w.read('X_Text[0-9]') assert conn_w.readable( timeout=1.0 ), "Failed to receive reply 1" rpy1 = next( conn_w ) assert conn_w.readable( timeout=1.0 ), "Failed to receive reply 2" rpy2 = next( conn_w ) with client.connector (host=host) as conn_r: for index, descr, op, reply, status, value in conn_r.pipeline( operations=client.parse_operations(tags), depth=2): print("%20s: %s" % (descr, value)) raw=("%s" % (value[2])) plcValue1=(raw) print(raw) print("Read Done")
# path = [ # # {'symbolic': 'A63FGRDT'}, # {'class':1}, {'instance':1},# {'attribute':7}, # ] ) else: route_path, send_path = None, None # Routed (eg. C*Logix) #route_path, send_path = defaults.route_path_default, defaults.send_path_default connection = client.connector( host = hostname, timeout = timeout, sender_context = sender_context, ) with connection as conn: data = True while True: # Perform some I/O on the Connected channel begun = cpppo.timer() operations = client.parse_operations( params, route_path=route_path, send_path=send_path ) failed,txs = conn.process( operations=operations, depth=depth, multiple=multiple, fragment=fragment, printing=printing, timeout=timeout ) elapsed = cpppo.timer() - begun # Now, wait for spontaneous data data,elapsed = client.await_response( conn, timeout=1 ) if data: log.normal( "Received: {data!r}".format( data=data ))
def hart_pass_thru(io, path, hart_data, data_size, route_path=None): """For eg. hart_data=[1, 0], data_size=4 for HART command 1. Returns None on failure, or the HART command response data payload. Harvests a Pass-thru Init handle, and issues Query on it 'til successs. """ # Try to start the Pass-thru command, with the Pass-thru Init, and get handle operations = [ { "method": "service_code", "code": HART.PT_INI_REQ, "data": hart_data, "data_size": 4 + 2, # Known response size: command,status,<payload> "path": path, # Instance 1-8 ==> HART Channel 0-7 "route_path": route_path, }, ] # Look for a reply init.status of 33 initiated. Actually, it appears that status 0 indicates success. handle = None while handle is None: time.sleep(.1) with io: for idx, dsc, req, rpy, sts, val in io.pipeline( operations=client.parse_operations(operations), **hart_kwds): log.detail("Client %s: %s --> %r: request: %s\nreply:%s", io, dsc, val, enip.enip_format(req), enip.enip_format(rpy)) if rpy.status == 0 and rpy.init.status in ( 33, ): # 32 busy, 33 initiated, 35 device offline handle = rpy.init.handle log.normal("HART Pass-thru command Handle: %s", handle) # Query for success/failure (loop on running) operations = [ { "method": "service_code", "code": HART.PT_QRY_REQ, "data": [handle], # HART: Pass-thru Query handle "data_size": 4 + data_size, # Known response size: 5 (units + 4-byte real in network order) "path": path, # Instance 1-8 ==> HART Channel 0-7 "route_path": route_path, }, ] reply = {} while not reply or (reply.status == 0 and reply.query.status == 34): # 0 success, 34 running, 35 dead time.sleep(.1) with io: for idx, dsc, req, rpy, sts, val in io.pipeline( operations=client.parse_operations(operations), **hart_kwds): log.detail("Client %s: %s --> %r: %s", io, dsc, val, enip.enip_format(rpy)) reply = rpy log.normal("HART pass-thru command Status: %s", reply.get('query.status')) return reply.get('query.reply_data.data', None)