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

cpppo with OMRON PLC #35

Open vinssurfer opened 7 years ago

vinssurfer commented 7 years ago

Hi, I try to use cpppo with OMRON PLC (CJ1, CJ2 and NJ with ethernet card EIP21), but I can't read/write memory. With this command, I can see product on the network :

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

 List Identity  0 from ('192.168.0.2', 44818): {
     "count": 1,
     "item[0].type_id": 12,
     "item[0].length": 44,
     "item[0].identity_object.version": 1,
     "item[0].identity_object.sin_family": 2,
     "item[0].identity_object.sin_port": 44818,
     "item[0].identity_object.sin_addr": "192.168.0.2",
     "item[0].identity_object.vendor_id": 47,
     "item[0].identity_object.device_type": 12,
     "item[0].identity_object.product_code": 13,
     "item[0].identity_object.product_revision": 258,
     "item[0].identity_object.status_word": 53,
     "item[0].identity_object.serial_number": 8822956,
     "item[0].identity_object.product_name": "CJ2B-EIP21",
     "item[0].identity_object.state": 3
 }
 List Identity  1 from ('192.168.0.4', 44818): {
     "count": 1,
     "item[0].type_id": 12,
     "item[0].length": 46,
     "item[0].identity_object.version": 1,
     "item[0].identity_object.sin_family": 2,
     "item[0].identity_object.sin_port": 44818,
     "item[0].identity_object.sin_addr": "192.168.0.4",
     "item[0].identity_object.vendor_id": 47,
     "item[0].identity_object.device_type": 12,
     "item[0].identity_object.product_code": 1613,
     "item[0].identity_object.product_revision": 257,
     "item[0].identity_object.status_word": 48,
     "item[0].identity_object.serial_number": 9243817,
     "item[0].identity_object.product_name": "NS10-TV01-V2",
     "item[0].identity_object.state": 3
 }

But when I try to read/write memory area, cpppo return None

python -m cpppo.server.enip.get_attribute -a 192.168.0.2 "@0xC4/0x10/3"
Tue Jun 20 08:29:56 2017:   0: Single G_A_S      @0x00C4/16/3 == None

In the EIP21 documentation, to read CPU unit mode, I have these informations :

Service code = 0x0E Class ID = 0xC4 Instance ID = 0x00 Request service data Attribute ID = 0x64

Other exemple to write a byte data

Service code = 0x1C Class ID = 0xC4 Instance ID = Specifies area (01 Hex to 14 Hex) Request service data Attribute ID = Address, No. of read bytes

What's the commande to read byte area memory with a service code?

Normally default route path is ok for my configuration (I have de CPU and next to the EIP21 ethernet card with unit number 0). I try to specifie de route path, but cpppo return an error :

python -m cpppo.server.enip.get_attribute -a 192.168.0.2 --route-path '[{"link": 0, "port": 1}]' "@0xC4/0x10/3"
Traceback (most recent call last):
  File "C:\Users\vinssurfer\AppData\Local\Programs\Python\Python36-32\lib\r
unpy.py", line 193, in _run_module_as_main
    "__main__", mod_spec)
  File "C:\Users\vinssurfer\AppData\Local\Programs\Python\Python36-32\lib\r
unpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "C:\Users\vinssurfer\AppData\Local\Programs\Python\Python36-32\lib\s
ite-packages\cpppo-3.9.7-py3.6.egg\cpppo\server\enip\get_attribute.py", line 803
, in <module>
    sys.exit( main() )
  File "C:\Users\vinssurfer\AppData\Local\Programs\Python\Python36-32\lib\s
ite-packages\cpppo-3.9.7-py3.6.egg\cpppo\server\enip\get_attribute.py", line 758
, in main
    route_path                  = json.loads( args.route_path ) if args.route_pa
th \
  File "C:\Users\vinssurfer\AppData\Local\Programs\Python\Python36-32\lib\j
son\__init__.py", line 354, in loads
    return _default_decoder.decode(s)
  File "C:\Users\vinssurfer\AppData\Local\Programs\Python\Python36-32\lib\j
son\decoder.py", line 339, in decode
    obj, end = self.raw_decode(s, idx=_w(s, 0).end())
  File "C:\Users\vinssurfer\AppData\Local\Programs\Python\Python36-32\lib\j
son\decoder.py", line 357, in raw_decode
    raise JSONDecodeError("Expecting value", s, err.value) from None
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)

Can you help me to connect cpppo with OMRON plc?

Thanks.

pjkundert commented 7 years ago

To see what error codes the Omron is returning from the get_attribute request, add some "-v" options to the command.

As for the --route-path: it appears that (for some reason) the JSON specification for the route path you are supplying is not being parsed correctly, by the Python 'json' module. It could be due to invalid characters (non-ASCII quote symbols, for example). Windows is notorious for this, unfortunately, and getting quoting right on the command line is extremely difficult. Is it possible to try it from a Linux host, or on a Mac?

vinssurfer commented 7 years ago

Thanks for your answer. I'm french so mayby quote is not same ASCII code. But I can't try with Linux or Mac.

That's the result with -vv

python -m cpppo.server.enip.get_attribute -a 192.168.0.7 -vv "@0xC4/0x10/3"
06-20 15:54:34.400 MainThread enip.cli DETAIL   __init__   Connect:  TCP/IP to ('192.168.0.7', 44818)
06-20 15:54:34.478 MainThread enip.cli DETAIL   cip_send   Client CIP Send: {
    "enip.session_handle": 0,
    "enip.options": 0,
    "enip.status": 0,
    "enip.sender_context.input": "bytearray(b'\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00')",
    "enip.CIP.register.options": 0,
    "enip.CIP.register.protocol_version": 1
}
06-20 15:54:34.494 MainThread enip.cli NORMAL   __init__   Connect:  Success in  0.093s/  5.000s
06-20 15:54:34.494 MainThread enip.cli DETAIL   parse_oper Tag: '@0xC4/0x10/3' yields Operation: {'path': [{'class': 196}, {'instance': 16}, {'attribute': 3}]}.update({'route_path': None, 'send_path': None})
06-20 15:54:34.494 MainThread enip.cli DETAIL   cip_send   Client CIP Send: {
    "enip.session_handle": 132096,
    "enip.options": 0,
    "enip.status": 0,
    "enip.sender_context.input": "bytearray(b'0\\x00\\x00\\x00\\x00\\x00\\x00\\x00')",
    "enip.CIP.send_data.interface": 0,
    "enip.CIP.send_data.timeout": 0,
    "enip.CIP.send_data.CPF.item[0].type_id": 0,
    "enip.CIP.send_data.CPF.item[1].type_id": 178,
    "enip.CIP.send_data.CPF.item[1].unconnected_send.service": 82,
    "enip.CIP.send_data.CPF.item[1].unconnected_send.status": 0,
    "enip.CIP.send_data.CPF.item[1].unconnected_send.priority": 5,
    "enip.CIP.send_data.CPF.item[1].unconnected_send.timeout_ticks": 157,
    "enip.CIP.send_data.CPF.item[1].unconnected_send.path.segment[0].class": 6,
    "enip.CIP.send_data.CPF.item[1].unconnected_send.path.segment[1].instance":1,
    "enip.CIP.send_data.CPF.item[1].unconnected_send.route_path.segment[0].link": 0,
    "enip.CIP.send_data.CPF.item[1].unconnected_send.route_path.segment[0].port": 1,
    "enip.CIP.send_data.CPF.item[1].unconnected_send.request.path.segment[0].class": 196,
    "enip.CIP.send_data.CPF.item[1].unconnected_send.request.path.segment[1].instance": 16,
    "enip.CIP.send_data.CPF.item[1].unconnected_send.request.path.segment[2].attribute": 3,
    "enip.CIP.send_data.CPF.item[1].unconnected_send.request.get_attribute_single": true,
    "enip.CIP.send_data.CPF.item[1].unconnected_send.request.service": 14,
    "enip.CIP.send_data.CPF.item[1].unconnected_send.request.input": "bytearray(b'\\x0e\\x03 \\xc4$\\x100\\x03')"
}
06-20 15:54:34.494 MainThread enip.cli DETAIL   format_pat Formatted
         @0x00C4/16/3 from: [{'class': 196}, {'instance': 16}, {'attribute': 3}]

06-20 15:54:34.494 MainThread enip.cli DETAIL   issue      Sent   0.003/  5.000s
: Single G_A_S      @0x00C4/16/3 {
    "path.segment[0].class": 196,
    "path.segment[1].instance": 16,
    "path.segment[2].attribute": 3,
    "get_attribute_single": true,
    "service": 14,
    "input": "bytearray(b'\\x0e\\x03 \\xc4$\\x100\\x03')"
}
06-20 15:54:34.525 MainThread enip.cli DETAIL   issue      Sending  1 (Context     b'0')
06-20 15:54:34.525 MainThread enip.cli DETAIL   pipeline   Issuing     0/  1; curr:   0 - last:  -1 ==   1 depth
06-20 15:54:34.541 MainThread enip.cli DETAIL   collect    Rcvd   0.006/  5.000s
 {
    "peer": [
        "192.168.0.7",
        44818
    ],
    "enip.command": 111,
    "enip.length": 20,
    "enip.session_handle": 132096,
    "enip.status": 0,
    "enip.sender_context.input": "array('B', [48, 0, 0, 0, 0, 0, 0, 0])",
    "enip.options": 0,
    "enip.input": "array('B', [0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 178, 0, 4, 0, 142, 0, 5, 0])",
    "enip.CIP.send_data.interface": 0,
    "enip.CIP.send_data.timeout": 0,
    "enip.CIP.send_data.CPF.count": 2,
    "enip.CIP.send_data.CPF.item[0].type_id": 0,
    "enip.CIP.send_data.CPF.item[0].length": 0,
    "enip.CIP.send_data.CPF.item[1].type_id": 178,
    "enip.CIP.send_data.CPF.item[1].length": 4,
    "enip.CIP.send_data.CPF.item[1].unconnected_send.request.input": "array('B', [142, 0, 5, 0])",
    "enip.CIP.send_data.CPF.item[1].unconnected_send.request.service": 142,
    "enip.CIP.send_data.CPF.item[1].unconnected_send.request.status": 5,
    "enip.CIP.send_data.CPF.item[1].unconnected_send.request.status_ext.size": 0

}
06-20 15:54:34.650 MainThread enip.cli DETAIL   collect    Receive  1 (Context     b'0')
06-20 15:54:34.665 MainThread enip.cli DETAIL   pipeline   Completed   1/  1; curr:   0 - last:   0 ==   0 depth
Tue Jun 20 15:54:34 2017:   0: Single G_A_S      @0x00C4/16/3 == None
06-20 15:54:34.681 MainThread enip.cli DETAIL   pipeline   Pipelined   1/  1; curr:   0 - last:   0 ==   0 depth
06-20 15:54:34.681 MainThread enip.get NORMAL   main         1 requests in   0.192s at pipeline depth  0;   5.198 TPS

Does "service": 14, corresponding to Service code in Omron documentation? How can I change this value?

Thanks

pjkundert commented 7 years ago

Decimal 14 is hex 0x0E, so that's OK. I don't know if the OMRON does "routing" CIP requests (ie. with a route path encapsulation); perhaps try the "simple" non-routing CIP requests, by specifying the -S option to the ...get_attribute command. This eliminates the route-path and send-path encapsulation from the CIP request, and expects the receiving EtherNet/IP module to respond to the CIP request itself...

vinssurfer commented 7 years ago

Yes I have an answer with -S option

With this command I can read which mode the PLC is (run, program, monitor)

python -m cpppo.server.enip.get_attribute -a 192.168.0.7 -S "@0xC4/0x0/0x64"
Tue Jun 20 16:45:53 2017:   0: Single G_A_S      @0x00C4/0/100 == [1, 0]

but I can't not access to the memory area EIP21 omron.pdf I will continue on Thursday...

vinssurfer commented 7 years ago

Hi, I have no problem to read/write plc operating mode (service 0x0E and Ox10) But to read/write memory or input/output, I need to change service code (service code 1C to 1F). You can see that in my previous post in the pdf file. How can I change the service code? Is it an other function than get_attrribute? Thanks

vinssurfer commented 7 years ago

Hello, I renew my question : is-it possible to change the service code and how? Thanks

je13fr commented 5 years ago

I have this code to read attribute and if I remember me well it was due to this error (routing=false)

import socket import time import cpppo from cpppo.server.enip import client

plc_ip_address = "192.168.1.10" timeout = 5.0

import logging cpppo.log_cfg['level'] = logging.ERROR

logging.basicConfig( **cpppo.log_cfg ) try: with client.connector( host=plc_ip_address, timeout=timeout) as conn: get_attr_single_operation = { \ 'path': [{'class': 4}, {'instance': 101}, {'attribute': 3}], \ 'method': 'get_attribute_single', \ 'send_path': '', 'route_path': False \ } failures,replies = conn.process( operations=[get_attr_single_operation], timeout=timeout ) for rpy in replies: print(rpy)

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

narogon commented 5 years ago

I have exactly the same problem with Pepperl Fuchs IO link module. The service to access the data is 0x4b not 0x0E (get single attribute) as said in documentation page 53

manual tdoct5792a_eng.pdf