Correct way of creating a networked RAPID application/module

pollux Netherlands
edited November 2014 in RobotStudio
Dear reader,

After a day of diving really into RAPID and getting some test applications running I'm looking into the second big part of my application. For the project I'm working on I want to be able to send positions to the robot which to which is needs to move. All positions will lay on the same YZ-plane and are in the reach of the robot. I already got an application ready which can send these coordinates to a tcp/ip server. 

Normally (with C/C++) I would either use a asynchronous socket or a separate thread in which I receive data and share received data using muteces to protect it. When using RAPID I'm not sure what the best way is to handle this data. I want the robot to move as fast as possible and as "real time" as possible.  Can someone explain me roughly what I need to look into for this.

To give a bit more information... at this moment I'm thinking to use a simple binary protocol where I sent `messages` from my application to the robot. The first 4 bytes will hold the size of the message (big endian order) in bytes and once I receive a complete message I want to convert the received coordinates and use them with my robtargets. How can I read but not flush the first 4 bytes? And what would be the fastest/best solution to read numeric data? 



  • pollux
    pollux Netherlands
    After a day of experimenting I'll try to share some of my experience, if someone has any comments or ideas how to overcome the issues I describe below that would be great.

    - First I tried the poor mans approach where I read data using `SocketReceive` and stored it in a `rawbytes` variable. Though this variable can only hold 1024 bytes if I'm correct. Because I don't know how many bytes I'' receive exactly (this may be more then 1024 for sure), this doesn't seem to be a solid approach when dealing with the amount of data I need to handle. Also, using rawbytes makes the code a bit kludgy (this could be due to my lack of experience), because I couldn't find a way to flush the read bytes in a clean way. I'm copying the unread bytes into a new buffer by using the number of bytes read as the `fromIndex` of `CopyRawBytes`. 

    - I also tried to create a RECORD that can hold all the different packets/messages I want to sent. The I create a huge array and fill the correct index into this array. Then once I've received all data I iterate over the array and process all the packets. I think this is a doable approach but I ran into some wierd behavior of RobotStudio.

    - Currently I'm trying a very basic solution where I directly parse the incoming data which are just positions to which I want to move the robot towards. But it seems that the robot is simply ignoring or not reading all packets I'm sending. Could this be the case? Lets say the robot is moving and at the same time I'm sending e.g. 4096 bytes, could it be that the internal socket buffer simply overflows and flushes the data instead of handing it over to my `SocketReceive`? The code belows shows this solution. When I sends with 100ms delay between each position it works perfectly.  (Code is pure test code and not cleaned up).

    All suggestions are welcome :) 

    MODULE Server

        PERS tooldata myTool:=[TRUE,[[0,0,1],[1,0,0,0]],[1,[0,0,0.1],[1,0,0,0],0,0,0]];
        TASK PERS wobjdata myWobj:=[FALSE,TRUE,"",[[400,0,400],[1,0,0,0]],[[0,0,0],[1,0,0,0]]];
        VAR robtarget myRobtarget:=[[0,0,0],[1,0,0,0],[0,0,0,0],[9E9,9E9,9E9,9E9,9E9,9E9]];
        VAR socketdev server_socket;
        VAR socketdev client_socket;
        VAR bool server_created:=FALSE;

        VAR byte command:=255;
        VAR num peek_value:=0;
        VAR num read_offset:=1;
        VAR num bytes_available:=0;
        VAR pos read_position;
        VAR rawbytes raw_data_in;
        VAR rawbytes parse_data;
        VAR rawbytes tmp_data;

        VAR bool must_parse:=FALSE;
        VAR bool to_home:=TRUE;

        PROC main()

            IF to_home=TRUE THEN
                ! Move all axis into their start position.
                MoveAbsJ [[0,0,0,0,0,0],[9E9,9E9,9E9,9E9,9E9,9E9]],vmax,z0,tool0;

                ! We turn of `Configuration Joint` and `Configuration Linear` which 
                ! means that the robot can use different confirations than that which 
                ! was programmed. 

                ! Rotate the `tcp` around the world Y-axis so that X will point 
                ! down and Z forwad. This gives us move freedom to move the 
                ! robot. 
                MoveJ myRobtarget,vmax,fine,tool0\WObj:=myWobj;


            IF server_created=FALSE THEN
                SocketCreate server_socket;
                SocketBind server_socket,"",1025;
                SocketListen server_socket;
                SocketAccept server_socket,client_socket;
                TPWrite "Accepted a new connection";


            IF peek_value>=1 THEN

                ClearRawBytes raw_data_in;
                SocketReceive client_socket\RawData:=parse_data;

                IF bytes_available>=1 THEN
                    UnpackRawBytes parse_data,1,command\Hex1;
                    IF 0=command THEN
                        ! Read a position.      
                        UnpackRawBytes parse_data\Network,2,read_position.x\Float4;
                        UnpackRawBytes parse_data\Network,6,read_position.y\Float4;
                        UnpackRawBytes parse_data\Network,10,read_position.z\Float4;

                        MoveL Offs(myRobtarget,read_position.x, read_position.y, read_position.z),vmax,z0,tool0,\WObj:=myWobj; 
                        TPWrite "Unhandled command: "\Num:=command;

  • pollux
    pollux Netherlands
    Update 2:
    If I'm correct there is another way to handle networked data with RAPID which feels more like a solution I'm used to in other languages. One, can basically create a separate task (Controller tab > System > Conifguration > Controller > New Task) and then create a listening socket there. Then you receive data in that separate thread and use interrupts to synchronize with the main task. I haven't read up on how to do this exactly yet, but maybe someone around here with more experience can share some code snippets? I'll try to update later. 

  • pollux
    pollux Netherlands
    Update 3:
    Just a couple of notes for anyone who is diving into this area. But again, I'm a complete newb with ABB/RobotStudio/RAPID so if you have any improvements please add them to this thread (or please correct me if I'm wrong).  I got a first proof of concept working where I send position data over a socket to a robot. I'm using two tasks, one which receives the network data and parses incoming "commands". e.g. a command can contain a position, a I/O port toggle etc.. 

    For my solution I had to cheat a bit for now. One big problem with RAPID (atm) is that you cannot easily receive rawbytes with a length bigger then 1024 bytes. For this I had two solutions. The first one was to create my own buffer handling functions in RAPID for which I'm not yet feeling confident enough to write. The other solution which was way easier to implement is that I batch my send requests in my C/C++ application. So for now I'm sending every separate line segment individually. I'm also simplifying the points in the segment so that no two points are closer then a given minimum distance. This seems to work fine but it's of course far from perfect. A couple of improvements that I wish to make is that I can simply send an arbitrary sized buffer to the robot. Also I had to change the zonedata from z0 to fine to make sure the simulation in RobotStudio reflected the exact positions that I'm sending. When using z0 is seems that the simulation is running behind (probably due to the used time stamp value in the simulation) the actually used positions.

  • pollux
    pollux Netherlands
    Update 4:
    Just wanted to share the first result of my free drawing app. This was the very first things we drew :). I have to make a couple of improvements but we're getting there. So far thanks everyone for their valuable advise.

  • Ram
    Ram Malaysia
    Hi, im doing something similar but trying to send the coordinate in string. Is it possible the controller can interpret it?
  • SteveMob
    SteveMob USA
    edited April 2016

    I think I am trying to do something along the lines of what you are did here. I am trying to get the IRC5/RAPID to receive messages sent from LabVIEW using Sockets. These messages contain first a position in space we want the robot to move, then another message telling the robot to move to that position defined in the previous message. I am new to this sort of thing so I was wondering if you could kind of steer me in the right direction of what I need to do? I have the robot being the client, and the LabView computer being the server.

  • PerSvensson
    PerSvensson Sweden ✭✭✭
    I have done this a lot of times and I usually do the communication in a background task and the share the information with the motion task. I always define my protocol like start of message and then commands/data and last end of message. Another important thing is error handing if the client to server(or the opposite way) goes down you need to handle reconnects. When it comes to the command/data I usually define a record type for this.
    Per Svensson
    Robotics and Vision Specialist
    Consat Engineering
  • So I am not really sure what you mean by "define a record type for this". Can you show me an example or some clarification?

  • PerSvensson
    PerSvensson Sweden ✭✭✭

    A record type is a data type that you can define after your needs like this

    Per Svensson
    Robotics and Vision Specialist
    Consat Engineering
  • Is there any other functions besides the Offset that can handle Move commands? For instance, with the current code that Pollux has, it will take a X, Y, Z position input from the computer that is Offset from whatever the myRobtarget is defined as. Is there a function that can handle X, Y, Z as well as Quaternion angles and axis configs?

  • PerSvensson
    PerSvensson Sweden ✭✭✭
    a robtarget consist of 4 data types
    - pos (x,y,z)
    - orient (in quaternion, there are functions for going between euler (pose data type) and quaternion and the other way)
    - confdata (cf1,cf4,cf6,cfx)
    - extjoint (external axis) 
    you can define them all as seperate variables

    For the motion you can also use RelTool, which is in relation to the tool coordinate system and not the workobject as in Offs

    Everything is described in the manual Rapid instructions, Functions and Data types
    Per Svensson
    Robotics and Vision Specialist
    Consat Engineering
  • Got it thanks. One more question, I am working to get it to send bytes declaring the pos, orient, confdata, etc... Is there a way to predetermine what the configuration will be? I understand the orientation, but we can't just give it an arbitrary configuration, it will maybe have a couple of options, but if we are sending it bytes declaring what the configuration is, how would we know what configuration we will need to send it?

  • PerSvensson
    PerSvensson Sweden ✭✭✭
    Even if configurations are easy to tell axis by axis it's not so easy to predetermine. One way is to run the program with configuration turned off (see ConfL and ConfJ) it means that it doesn't matter what values you have in confdata because they aren't taking into calculation. But be aware of that axis could rotate in a direction you don't want. Example let's say that axis 4 is in 120 deg and you expect it to go to -120 in the next move but it will calculate that the closest way is to move to +240 deg (120 deg diff compared to 240 deg). So config is used to tell in which direction the axis should rotate 
    Per Svensson
    Robotics and Vision Specialist
    Consat Engineering
  • hasan
    hasan Korea
    Is there any improvement in data handling speed if the TCP/IP comm and data handling functions are moved to the background task.

    I am receiving large amounts of data (in access of 80000 robtargets) in the main task. I dont mind the task being blocked during data reception.
    The TCP/IP communication is fast enough but the unpacking (using UnpackRawBytes) and copying the incoming data to the robtarget buffer causes significant delays resulting in wait of 3~4 mins for data transfer. I guess the bottleneck is due to the processing/memory access capabilities of the IRC5 controller with the UnpackRawBytes instruction.

    Will the speed improve if I move the data reception and handling to the background task ? Following is the code for data reception and unpacking

                    WHILE (rcvPacketIndex<=rcvPacketNum) DO
                        SocketReceive clientSocket\RawData:=rawPacket\ReadNoOfBytes:=1024\Time:=WAIT_MAX; 
                        WHILE (pIndex<=1024) DO

                            UnpackRawBytes rawPacket,pIndex,buffNum\IntX:=1;
                            UnpackRawBytes rawPacket,pIndex+1,buffLnIdx\IntX:=2;
                            UnpackRawBytes rawPacket,pIndex+3,sPtIdx\IntX:=1;
                            !take out a target pt consisting of 12 floats (3 position and 4 rotations)
                            TEST buffNum
                            CASE 1:
                                UnpackRawBytes rawPacket,pIndex+4, scanGrid1{buffLnIdx,sPtIdx}.trans.x\Float4;
                                UnpackRawBytes rawPacket,pIndex+8, scanGrid1{buffLnIdx,sPtIdx}.trans.y\Float4;
                                UnpackRawBytes rawPacket,pIndex+12,scanGrid1{buffLnIdx,sPtIdx}.trans.z\Float4;
                                UnpackRawBytes rawPacket,pIndex+16,scanGrid1{buffLnIdx,sPtIdx}.rot.q1\Float4;
                                UnpackRawBytes rawPacket,pIndex+20,scanGrid1{buffLnIdx,sPtIdx}.rot.q2\Float4;
                                UnpackRawBytes rawPacket,pIndex+24,scanGrid1{buffLnIdx,sPtIdx}.rot.q3\Float4;
                                UnpackRawBytes rawPacket,pIndex+28,scanGrid1{buffLnIdx,sPtIdx}.rot.q4\Float4;
                            CASE 2:
                                UnpackRawBytes rawPacket,pIndex+4, scanGrid2{buffLnIdx,sPtIdx}.trans.x\Float4;
                                UnpackRawBytes rawPacket,pIndex+8, scanGrid2{buffLnIdx,sPtIdx}.trans.y\Float4;
                                UnpackRawBytes rawPacket,pIndex+12,scanGrid2{buffLnIdx,sPtIdx}.trans.z\Float4;
                                UnpackRawBytes rawPacket,pIndex+16,scanGrid2{buffLnIdx,sPtIdx}.rot.q1\Float4;
                                UnpackRawBytes rawPacket,pIndex+20,scanGrid2{buffLnIdx,sPtIdx}.rot.q2\Float4;
                                UnpackRawBytes rawPacket,pIndex+24,scanGrid2{buffLnIdx,sPtIdx}.rot.q3\Float4;
                                UnpackRawBytes rawPacket,pIndex+28,scanGrid2{buffLnIdx,sPtIdx}.rot.q4\Float4;
                            CASE 3:
                                UnpackRawBytes rawPacket,pIndex+4, scanGrid3{buffLnIdx,sPtIdx}.trans.x\Float4;
                                UnpackRawBytes rawPacket,pIndex+8, scanGrid3{buffLnIdx,sPtIdx}.trans.y\Float4;
                                UnpackRawBytes rawPacket,pIndex+12,scanGrid3{buffLnIdx,sPtIdx}.trans.z\Float4;
                                UnpackRawBytes rawPacket,pIndex+16,scanGrid3{buffLnIdx,sPtIdx}.rot.q1\Float4;
                                UnpackRawBytes rawPacket,pIndex+20,scanGrid3{buffLnIdx,sPtIdx}.rot.q2\Float4;
                                UnpackRawBytes rawPacket,pIndex+24,scanGrid3{buffLnIdx,sPtIdx}.rot.q3\Float4;
                                UnpackRawBytes rawPacket,pIndex+28,scanGrid3{buffLnIdx,sPtIdx}.rot.q4\Float4;
                            CASE 4:
                                UnpackRawBytes rawPacket,pIndex+4, scanGrid4{buffLnIdx,sPtIdx}.trans.x\Float4;
                                UnpackRawBytes rawPacket,pIndex+8, scanGrid4{buffLnIdx,sPtIdx}.trans.y\Float4;
                                UnpackRawBytes rawPacket,pIndex+12,scanGrid4{buffLnIdx,sPtIdx}.trans.z\Float4;
                                UnpackRawBytes rawPacket,pIndex+16,scanGrid4{buffLnIdx,sPtIdx}.rot.q1\Float4;
                                UnpackRawBytes rawPacket,pIndex+20,scanGrid4{buffLnIdx,sPtIdx}.rot.q2\Float4;
                                UnpackRawBytes rawPacket,pIndex+24,scanGrid4{buffLnIdx,sPtIdx}.rot.q3\Float4;
                                UnpackRawBytes rawPacket,pIndex+28,scanGrid4{buffLnIdx,sPtIdx}.rot.q4\Float4;

                            !!verbose - effects perfromance so disable for actual program
    !                        currentPos := readScanGrid(sLnIdx,sPtIdx);
    !                        TPWrite "packet no. "+NumToStr(rcvPacketIndex,0)+", buffNum : " + NumToStr(buffNum,0) + ", buffLnIdx : " + NumToStr(buffLnIdx,0) + ", sPtIdx : " + NumToStr(sPtIdx,0) + ", ScanGrid(targetPt) : "\Pos:=currentPos.trans;

                            !advance the counters
  • hasan
    hasan Korea
    I have done some performance evaluation from 'Device Browser' of the controller in robot studio.
    During the data reception, the Rapid Execution load goes to 100% while the CPU load keeps changing between 80% and 40 %.
    I wonder if there is still some room for optimization to decrease the data reception time. Thanks