nimbuscontrols / EIPScanner

Free implementation of EtherNet/IP in C++
https://eipscanner.readthedocs.io/en/latest/
MIT License
233 stars 93 forks source link

Universal Robots Implicit Messaging #36

Closed haellingsen closed 3 years ago

haellingsen commented 3 years ago

Hi I was wondering if you could give me a notch in the right direction to set up implicit messaging to a Universal Robots robot controller?

It does not support explicit messaging. Only Implicit messaging.

I have the following EDS file and it defines two assemblies but I'm not sure how to set up the connection and how to interpret the eds.

The implicit IO example code defines serial numbers as well, are these required?

EDS File snippets.... Also see attached complete document.

[Device]
        VendCode = 1399;
        VendName = "Universal Robots A/S";
        ProdType = 43;
        ProdTypeStr = "Generic Device";
        ProdCode = 1;
        MajRev = 1;
        MinRev = 1;
        ProdName = "Universal Robot";
        Catalog = "UR";
        Icon = "UR_64x64.ico";

.....

[Device Classification]
        Class1 = EtherNetIP;

......

[Assembly]
        Assem1 =
                "Input Assembly",
                "20 04 24 64 30 03",
                ,
                0x0000,
                ,,
                $ ROBOT
                8,Param1,
                8,Param2,
                16,,                    $ pad

                8,Param3,
                8,Param4,
                16,Param5,
.......

        Assem2 =
                "Output Assembly",
                "20 04 24 70 30 03",
                ,
                0x0000,
                ,,
                $ ROBOT
                8,Param501,
                24,,                    $ pad
                32,Param502,
                $ Outputs
                8,Param503,

........

[Connection Manager]
        Connection1 =
                0x04010002,             $ TRIGGER AND TRANSPORT MASK
                                        $     BIT=VAL DESCRIPTION
                                        $        0 = 0  (class 0:null)
                                        $        1 = 1  (class 1:dup. detect)
                                        $        2 = 0  (class 2:acknowledged)
                                        $        3 = 0  (class 3:verified)
                                        $        4 = 0  (class 4:non-block)
                                        $        5 = 0  (class 5:non-block, frag)
                                        $        6 = 0  (class 6:multicast, frag)
                                        $   7-15 = 0  (class  :reserved)
                                        $      16 = 1  (trigger: cyclic)
                                        $      17 = 0  (trigger: cos)
                                        $      18 = 0  (trigger: appl)
                                        $ 19-23 = 0  (trigger: reserved (must be zero))
                                        $      24 = 0  (transport type: listen-only)
                                        $      25 = 0  (transport type: input-only)
                                        $      26 = 1  (transport type: exclusive-owner)
                                        $      27 = 0  (transport type: redundant-owner)
                                        $ 28-30 = 0  (reserved (must be zero))
                                        $      31 = 1  (client = 0 / server = 1)
                0x44440405,             $ CONNECTION PARAMETERS BIT ASSIGNMENTS

                                        $     BIT=VAL DESCRIPTION
                                        $        0 = 1  (O=>T fixed)
                                        $        1 = 0  (O=>T variable)
                                        $        2 = 1  (T=>O fixed)
                                        $        3 = 0  (T=>O variable)
                                        $     4-7 = 0  (reserved (must be zero))
                                        $   8-10 = 4  (O=>T header (4 byte run/idle))
                                        $      11 = 0  (reserved (must be zero))
                                        $ 12-14 = 0  (T=>O header (pure data))
                                        $      15 = 0  (reserved (must be zero))
                                        $      16 = 0  (O=>T connection type: NULL)
                                        $      17 = 0  (O=>T connection type: MULTI)
                                        $      18 = 1  (O=>T connection type: P2P)
                                        $      19 = 0  (O=>T connection type: RSVD)
                                        $      20 = 0  (T=>O connection type: NULL)
                                        $      21 = 1  (T=>O connection type: MULTI)
                                        $      22 = 0  (T=>O connection type: P2P)
                                        $      23 = 0  (T=>O connection type: RSVD)
                                        $      24 = 0  (O=>T priority: LOW)
                                        $      25 = 0  (O=>T priority: HIGH)
                                        $      26 = 1  (O=>T priority: SCHEDULED)
                                        $      27 = 0  (O=>T priority: RSVD)
                                        $      28 = 0  (T=>O priority: LOW)
                                        $      29 = 0  (T=>O priority: HIGH)
                                        $      30 = 1  (T=>O priority: SCHEDULED)
                                        $      31 = 0  (T=>O priority: RSVD)
                Param999,,Assem2,       $ O=>T RPI, size in bytes, format
                Param999,,Assem1,       $ T=>O RPI, size in bytes, format
                ,,                      $ config part 1 (dynamic assemblies)
                ,,                      $ config part 2 (module configuration)
                "Exclusive Owner",      $ connection name
                "",                     $ Help string
                "20 04 24 01 2C 70 2C 64";    $ exclusive owner path
        Connection2 =
                0x02010002,             $ TRIGGER AND TRANSPORT MASK
                                        $     BIT=VAL DESCRIPTION
                                        $        0 = 0  (class 0:null)
                                        $        1 = 1  (class 1:dup. detect)
                                        $        2 = 0  (class 2:acknowledged)
                                        $        3 = 0  (class 3:verified)
                                        $        4 = 0  (class 4:non-block)
                                        $        5 = 0  (class 5:non-block, frag)
                                        $        6 = 0  (class 6:multicast, frag)
                                        $   7-15 = 0  (class  :reserved)
                                        $      16 = 1  (trigger: cyclic)
                                        $      17 = 0  (trigger: cos)
                                        $      18 = 0  (trigger: appl)
                                        $ 19-23 = 0  (trigger: reserved (must be zero))
                                        $      24 = 0  (transport type: listen-only)
                                        $      25 = 0  (transport type: input-only)
                                        $      26 = 1  (transport type: exclusive-owner)
                                        $      27 = 0  (transport type: redundant-owner)
                                        $ 28-30 = 0  (reserved (must be zero))
                                        $      31 = 1  (client = 0 / server = 1)
                0x44440305,             $ CONNECTION PARAMETERS BIT ASSIGNMENTS

                                        $     BIT=VAL DESCRIPTION
                                        $        0 = 1  (O=>T fixed)
                                        $        1 = 0  (O=>T variable)
                                        $        2 = 1  (T=>O fixed)
                                        $        3 = 0  (T=>O variable)
                                        $     4-7 = 0  (reserved (must be zero))
                                        $   8-10 = 4  (O=>T header (4 byte run/idle))
                                        $      11 = 0  (reserved (must be zero))
                                        $ 12-14 = 0  (T=>O header (pure data))
                                        $      15 = 0  (reserved (must be zero))
                                        $      16 = 0  (O=>T connection type: NULL)
                                        $      17 = 0  (O=>T connection type: MULTI)
                                        $      18 = 1  (O=>T connection type: P2P)
                                        $      19 = 0  (O=>T connection type: RSVD)
                                        $      20 = 0  (T=>O connection type: NULL)
                                        $      21 = 1  (T=>O connection type: MULTI)
                                        $      22 = 0  (T=>O connection type: P2P)
                                        $      23 = 0  (T=>O connection type: RSVD)
                                        $      24 = 0  (O=>T priority: LOW)
                                        $      25 = 0  (O=>T priority: HIGH)
                                        $      26 = 1  (O=>T priority: SCHEDULED)
                                        $      27 = 0  (O=>T priority: RSVD)
                                        $      28 = 0  (T=>O priority: LOW)
                                        $      29 = 0  (T=>O priority: HIGH)
                                        $      30 = 1  (T=>O priority: SCHEDULED)
                                        $      31 = 0  (T=>O priority: RSVD)
                Param999,0,,            $ O=>T RPI, size in bytes, format
                Param999,,Assem1,       $ T=>O RPI, size in bytes, format
                ,,                      $ config part 1 (dynamic assemblies)
                ,,                      $ config part 2 (module configuration)
                "Input Only",           $ connection name
                "",                     $ Help string
                "20 04 24 01 2C FE 2C 64";    $ exclusive owner path
        Connection3 =
                0x01010002,             $ TRIGGER AND TRANSPORT MASK
                                        $     BIT=VAL DESCRIPTION
                                        $        0 = 0  (class 0:null)
                                        $        1 = 1  (class 1:dup. detect)
                                        $        2 = 0  (class 2:acknowledged)
                                        $        3 = 0  (class 3:verified)
                                        $        4 = 0  (class 4:non-block)
                                        $        5 = 0  (class 5:non-block, frag)
                                        $        6 = 0  (class 6:multicast, frag)
                                        $   7-15 = 0  (class  :reserved)
                                        $      16 = 1  (trigger: cyclic)
                                        $      17 = 0  (trigger: cos)
                                        $      18 = 0  (trigger: appl)
                                        $ 19-23 = 0  (trigger: reserved (must be zero))
                                        $      24 = 0  (transport type: listen-only)
                                        $      25 = 0  (transport type: input-only)
                                        $      26 = 1  (transport type: exclusive-owner)
                                        $      27 = 0  (transport type: redundant-owner)
                                        $ 28-30 = 0  (reserved (must be zero))
                                        $      31 = 1  (client = 0 / server = 1)
                0x44440305,             $ CONNECTION PARAMETERS BIT ASSIGNMENTS

                                        $     BIT=VAL DESCRIPTION
                                        $        0 = 1  (O=>T fixed)
                                        $        1 = 0  (O=>T variable)
                                        $        2 = 1  (T=>O fixed)
                                        $        3 = 0  (T=>O variable)
                                        $     4-7 = 0  (reserved (must be zero))
                                        $   8-10 = 4  (O=>T header (4 byte run/idle))
                                        $      11 = 0  (reserved (must be zero))
                                        $ 12-14 = 0  (T=>O header (pure data))
                                        $      15 = 0  (reserved (must be zero))
                                        $      16 = 0  (O=>T connection type: NULL)
                                        $      17 = 0  (O=>T connection type: MULTI)
                                        $      18 = 1  (O=>T connection type: P2P)
                                        $      19 = 0  (O=>T connection type: RSVD)
                                        $      20 = 0  (T=>O connection type: NULL)
                                        $      21 = 1  (T=>O connection type: MULTI)
                                        $      22 = 0  (T=>O connection type: P2P)
                                        $      23 = 0  (T=>O connection type: RSVD)
                                        $      24 = 0  (O=>T priority: LOW)
                                        $      25 = 0  (O=>T priority: HIGH)
                                        $      26 = 1  (O=>T priority: SCHEDULED)
                                        $      27 = 0  (O=>T priority: RSVD)
                                        $      28 = 0  (T=>O priority: LOW)
                                        $      29 = 0  (T=>O priority: HIGH)
                                        $      30 = 1  (T=>O priority: SCHEDULED)
                                        $      31 = 0  (T=>O priority: RSVD)
                Param999,0,,            $ O=>T RPI, size in bytes, format
                Param999,,Assem1,       $ T=>O RPI, size in bytes, format
                ,,                      $ config part 1 (dynamic assemblies)
                ,,                      $ config part 2 (module configuration)
                "Listen Only",          $ connection name
                "",                     $ Help string
                "20 04 24 01 2C FF 2C 64";    $ exclusive owner path

[Port]
        Port1 =
                TCP,
                "EtherNet/IP Port",
                "20 F5 24 01",
                1;

[Capacity]
        MaxIOConnections = 2;
        MaxMsgConnections = 2;
        MaxConsumersPerMcast = 2;
        TSpec1 = Rx, 224, 1000;
        TSpec2 = Tx, 480, 1000;

UniversalRobots.txt

jadamroth commented 3 years ago

Hi Harald,

Thanks for reaching out. I haven't yet heard of anyone using our library with a UR robot, so happy to hear that it's being included for this use case.

First off, I'm assuming you've already viewed the implicit messaging example

Before we go into detail of the EDS, I'll ask a few things

  1. Will you provide some more specific information of what your end goal is?
  2. Do you have an UR EtherNet/IP data sheet or any documentation besides the eds which you can attach here

I'd recommend you to look at this issue to see how they figured out implicit messaging with another end-device:

haellingsen commented 3 years ago

Hi Adam,

First of all, I highly appreciate you taking the time to address my question.

I did look at the implicit messaging example buy I'm having a hard time understanding the path parameters.

The yaskawa issue #30 is very similar, I also get the same error message as he describes initially. However did they end up with using implicit messaging in the end?

To answer your questions:

  1. First I want to be able to monitor and manipulate IO on the robot from the application. Then I want to implement a gRPC interface to the EIP communication.
  2. Guide for connecting to an Allen Bradley PLC: https://www.universal-robots.com/articles/ur/ethernet-ip-guide/ Details of the io message: https://s3-eu-west-1.amazonaws.com/ur-support-site/18712/eip-iomessage.pdf
jadamroth commented 3 years ago

Yes, they started to use implicit messaging in issue 30 but ended up using explicit messaging. The Assembly object shows how data is structured in your end device. Read this article to understand more about Assembly Object and its purpose in EIP

From the EDS you sent me, there's two Assembly Objects: 1 for inputs and 1 for outputs. There's a lot of parameters in this UR robot, so you'll first need to go through and decide what you actually want to read/write. From the EDS file, you'll find the information to modify the code in the implicit messaging example for your own use case - https://github.com/nimbuscontrols/EIPScanner/blob/master/examples/ImplicitMessagingExample.cpp

So it will be something like this below, but please modify according to your needs and the comments provided:

auto si = std::make_shared<SessionInfo>("172.28.1.3", 0xAF12);  // ports for EIP are usually 44818 and 2222
ConnectionManager connectionManager;

ConnectionParameters parameters;
// modify below according to the connection manager, input/output assemblies and parameters you want to use
parameters.connectionPath = {0x20, 0x04, 0x24, 0x01, 0x2C, 0x70, 0x2C, 0x64}; 
parameters.o2tRealTimeFormat = true;
// some adapters require these parameters
//parameters.originatorVendorId = 342;  
//parameters.originatorSerialNumber = 32423;
parameters.t2oNetworkConnectionParams |= NetworkConnectionParams::P2P;
parameters.t2oNetworkConnectionParams |= NetworkConnectionParams::SCHEDULED_PRIORITY;
 // I got this from ParamX - data size in bytes
parameters.t2oNetworkConnectionParams |= 4;
parameters.o2tNetworkConnectionParams |= NetworkConnectionParams::P2P;
parameters.o2tNetworkConnectionParams |= NetworkConnectionParams::SCHEDULED_PRIORITY;
// I got this from ParamX - data size in bytes
parameters.o2tNetworkConnectionParams |= 4;  

parameters.o2tRPI = 1000000;
parameters.t2oRPI = 1000000;

I would recommend to start by retrieving an output which you know from the robot to confirm that you have the proper path, then you can move along to writing values to the robot.

Another assumption is that you're using unicast for IO messaging. Multicast has not yet been properly implemented or tested within this library.

haellingsen commented 3 years ago

Thank you.

I read the article and I tried your code and still have som issues though.

I got the followning console output.

[DEBUG] Opened socket fd=3
[DEBUG] Connecting to 192.168.254.10:44818
[INFO] Registered session 14
[INFO] Send request: service=0x54 epath=[classId=6 objectId=1]
[ERROR] Message Router error=0x4 additional statuses 
[WARNING] Attempt to close an already closed connection
[INFO] Unregistered session 14
[DEBUG] Close socket fd=3

Additionally in wireshark I can see that I get an error message stating: Service: Unknown Service (0x54) (Request) under Common Industrial Protocol header in this packet 13 6.976208 192.168.254.99 192.168.254.10 CIP CM 168 Connection Manager - Forward Open (Assembly) Micro-St_84:8b:a0

following that package is a Path segment error. 14 6.977665 192.168.254.10 192.168.254.99 CIP CM 118 Path segment error: Connection Manager - Forward Open Micro-St_bc:52:1e

Regarding the connectionPath:

parameters.connectionPath = {0x20, 0x04, 0x24, 0x01, 0x2C, 0x70, 0x2C, 0x64}; 

I could not find any description of the 0x2C. Is this to define additional Assembly Instances?

Does this mean I want to set up class 4, instance 1, instance 70 and instance 64?

How are the attributes addressed? I've seen in connectionPaths that 0x30 and has been listed, but here we are only listing the class and instances as parts of the path.

Are the amount of attributes being fetched determined by the data size from this statement; parameters.t2oNetworkConnectionParams |= 4 ?

Please see capture from wireshark on link below: eipscanner_ur_4bytes.zip

jadamroth commented 3 years ago

I think you're confusing the explicit messaging path and implicit messaging path. Let me start by clarifying a few things

What is CIP Do you understand CIP (common industrial protocol) and how data is structured internally within CIP? EtherNet/IP is just a protocol requesting CIP structured data over ethernet, just as DeviceNet is a protocol requesting CIP structured data over canbus. If not, here is a good article that explains CIP - https://www.rtautomation.com/rtas-blog/what-is-cip/

EtherNet/IP Explicit Messaging Explicit messaging is a tcp request to get data from a CIP object. Here is the EIP datasheet of a VFD that I frequently use - https://literature.rockwellautomation.com/idc/groups/literature/documents/um/520com-um001_-en-e.pdf

Go to page 132. This is an example of the Parameter object stored in our VFD. Examples of parameters might be frequency, torque, rpm, etc. Here's a list of parameters, so you can follow along - https://literature.rockwellautomation.com/idc/groups/literature/documents/du/520-du001_-en-e.pdf

Explicit messaging paths contain 3 things to retrieve data. ClassObject, InstanceID, AttributeID (in that order in our code's library). So if we want to make an explicit messaging request to get Output Current from the VFD, the epath is EPath(ParameterObject, ParameterNumber, AttributeID) = EPath(15, 3, 1)

EtherNet/IP Implicit Messaging Implicit messaging (or IO messaging) is a udp request to read/write data to a device. The device controlling the connection is the Scanner (hence the name of the library EIPScanner) and the device we connect to is the adapter (this is what the OpENer repo does). In your example, scanner is this library, UR robot is the adapter.

Implicit messaging is handled differently than requesting data from CIP objects via explicit messaging. The scanner first connects to the adapter, and then we read/write values over udp once we have a connection rather than making client/server tcp requests. These are often time-critical applications, and we need to handle these connections in real time - imagine controlling a robot with tcp requests, it probably wouldn't work.

All the information we need for implicit messaging is in the EDS (ConnectionManager, Assembly, and Params). The ConnectionManager is where we get our initial connection path. You have 3 connection managers, I chose Connection1 (since it is the exclusive owner) which has the $path = {20 04 24 01 2C 70 2C 64} on line 2979. You have two assemblies, one for inputs and one for outputs (I would start with reading outputs). You'll need to determine which params you want to read/write to the adapter, which are listed in each Assembly and referenced in the params.

jadamroth commented 3 years ago

Hi @haellingsen

Did you ever solve this issue/can I close this issue?

haellingsen commented 3 years ago

Did not solve, lack og time. U can close if u want.

Last ned Outlook for iOShttps://aka.ms/o0ukef


Fra: Adam Roth @.> Sendt: Tuesday, April 27, 2021 5:35:52 PM Til: nimbuscontrols/EIPScanner @.> Kopi: Harald Ellingsen @.>; Mention @.> Emne: Re: [nimbuscontrols/EIPScanner] Universal Robots Implicit Messaging (#36)

Hi @haellingsenhttps://github.com/haellingsen

Did you ever solve this issue/can I close this issue?

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHubhttps://github.com/nimbuscontrols/EIPScanner/issues/36#issuecomment-827704773, or unsubscribehttps://github.com/notifications/unsubscribe-auth/AALF7GNEJ6EWNPZDHYJDOULTK3KVRANCNFSM4U35BFHQ.

jadamroth commented 3 years ago

I'll close for now, but feel free to open this again in the future