Skip to content

Adding new motion program commands

John Wason edited this page May 17, 2024 · 3 revisions

abb_motion_program_exec works by creating a simple interpreter on the robot. The interpreter is written in RAPID and is installed using the setup instructions provided with the software. The Python module is used to create a list of commands, and these commands are converted to a binary file made up of a flat float32 array. The file is then downloaded to the robot RAM drive. The Python module triggers the robot to begin executing the RAPID program once the file is ready and downloaded to the robot. The RAPID software reads through the binary float32 file and runs the commands.

Each command has the following binary format:

Field Type Description
command_number FLOAT32 The sequence number of the command. Must be strictly increasing
command_opcode FLOAT32 The opcode of the command. The opcode selects which command to execute
params x Variable number of parameters following the opcode. Byte length must be divisible by 4

The parameters are variable length containing the data required to execute the command. The parameters are generally FLOAT32, but can also be other data types such as strings. All data must be aligned to 4 bytes. Strings must be padded to maintain this alignment.

The list of commands can also be converted to RAPID code

Adding new commands

Adding new motion program commands requires the following steps:

  • Review existing commands. See commands.py and egm_commands.py for the existing commands in src/abb_motion_program_exec/commands. Each command is implemented as a class with methods to write the binary parameters and to generate RAPID code. Utility functions for writing parameters can be found in util.py and rapid_types.py.
  • Select an opcode that is not being used by the existing commands.
  • Implement the new class for the new command with the selected opcode.
  • Add your class to the MotionProgram class in src/abb_motion_program_exec/abb_motion_program_exec_client.py. For example, the MoveJ command is added to the class using MoveJ = command_append_method(commands.MoveJCommand)
  • Add the decoding function for your command to robot/HOME/motion_program_exec.mod. The command should use the try_motion_program_read_num() to read FLOAT32 from the file. The function should execute the command if decoded successfully, and return FALSE if decoding failed.
  • Add your command to the CASE statement in try_motion_program_run_next_cmd(). This function implements the interpreter by reading the next opcode and then executing the appropriate decoder function.

At this point the new command should be successfully implemented. It is highly recommended that the software be tested in simulation using Robot Studio before using on a physical robot.

Adding New Commands to Robot Raconteur Service

The Robot Raconteur interface provides a service that executes motion programs. The service uses the MotionProgram and MotionProgramExecClient classes to implement the service. The Robot Raconteur types used for the service are defined in the experimental.robotics.motion_program.robdef and experimental.abb_robot.motion_program.robdef files. The experimental.robotics.motion_program.robdef contains vendor-independent data types and objects that are intended to eventually become a Robot Raconteur standard type after further development. The experimental.abb_robot.motion_program.robdef file contains motion program commands that are specific to ABB robots. The structure type experimental.robotics.motion_programs.MotionProgram Robot Raconteur type is used to define motion programs. The two fields of interest are motion_setup_commands and motion_program_commands. Motion programs are not intended to contain operations such as tool changes are logical branches. It is intended that the motion programs are relatively short sequences that are generated by the client. For this reason, the motion program is broken up into "setup" and "motion" commands. Setup commands contain information such as the tool, payload, and frame specifications. The motion section contains the commands that are to be executed by the robot.

Each command is defined using a structure. The motion_setup_commands and motion_program_commnds fields contain a list of structure instances, with each list entry containing a command. The Robot Raconteur varvalue{list} type is used to store the list of commands, allowing any valid Robot Raconteur structure to be added to the list. Example command motion command structures include experimental.robotics.motion_program.MoveJCommand, experimental.robotics.motion_program.MoveLCommand. Example setup instructions include experimental.robotics.motion_program.SetToolCommand. Example ABB specific command structures include experimental.abb_robot.motion_program.EGMMoveLCommand for motion and experimental.abb_robot.motion_program.SetWorkObjectCommand for setup.

The Robot Raconteur motion program types also includes the experimental.robotics.motion_program.FreeformCommand structure. The freeform command is a catch-all command that allows any command to be specified without using a specialized structure. This allows for greater flexibility for software generating motion program commands. For instance, if a vendor specific command needs to be used but the software is not compiled to use that specific type, the user could use a FreeformCommand to enter the details of the command without requiring a specialized structure. The FreeformCommand can also be used to add specialized commands without needing to modify the Robot Raconteur service definition files or the client software. The FreeformCommand contains three important fields, the command_name, command_args, and optional. The command_name is the name of the command, for instance MoveL, MoveLCommand, or experimental.robotics.motion_program.MoveLCommand are all valid for command_name. The command_args is a dictionary of varvalue containing the fields that are in the command. For MoveL, the field entries would include tcp_pose, tcp_velocity, blend_radius, and fine_point. The optional field, when set to False, means that the command can be ignored if the robot does not understand the command.

The Robot Raconteur experimental.robotics.motion_programs.MotionProgram structure and its contents must be converted to the Python MotionProgram types to be executed by the MotionProgramExecClient. This conversion is implemented in the src/abb_motion_program_exec/robotraconteur/_motion_program_conv.py file. Each command has a class defined for the conversion. For example, the MoveLCommandConv class is as follows:

class MoveLCommandConv:
    rr_types = ["experimental.robotics.motion_program.MoveLCommand"]
    freeform_names = ["MoveL","MoveLCommand","experimental.robotics.motion_program.MoveLCommand"]

    def apply_rr_command(self, cmd, mp, cfx_robot, **kwargs):
        zd = rr_zone_to_abb(cmd_get_arg(cmd,"fine_point"),cmd_get_arg(cmd,"blend_radius"))
        sd = rr_speed_to_abb(cmd_get_arg(cmd,"tcp_velocity"))
        rt = rr_robot_pose_to_abb(cmd_get_arg(cmd,"tcp_pose"), cfx_robot, cmd_get_extended(cmd, "confdata"))
        mp.MoveL(rt, sd, zd)

The first two fields are rr_types and freeform_names. The rr_types field is a list of the Robot Raconteur types defined in robdef files. In this case, experimental.robotics.motion_program.MoveLCommand is the fully qualified name of the MoveLCommand. The freeform_names field is a list of names that will match command_name in the FreeformCommand structure. The apply_rr_command() function is called when the motion program conversion matches a command to this class. The cmd parameter is the matched command instance, and the mp parameter is an instance of MotionProgramExecClient. For MoveLCommand, the zone data, speed data, and robot target are converted from Robot Raconteur to ABB motion program format using utility functions defined earlier in the _motion_program_conv.py. These functions are rr_zone_to_abb(), rr_speed_to_abb(), and rr_robot_pose_to_abb().

Note that ABB types use millimeters and degrees, while Robot Raconteur uses MKS system meters and radians following ROS REP-103!!

Note the use of cmd_get_arg() to access fields from the command instance cmd. This function will automatically find the field if it is in a specialized type of a FreeformCommand structure.

Once the class is inserted into the _motion_program_conv.py file, add the command to the list conv_types later in the file. This list is searched by the motion program conversion to match to an incoming command based on rr_types and freeform_names. Currently the conv_types list has the following entries:

conv_types = [
    MoveAbsJCommandConv,
    MoveJCommandConv,
    MoveLCommandConv,
    MoveCCommandConv,
    WaitTimeCommandConv,
    SetToolCommandConv,
    SetPayloadCommandConv,
    CirPathModeCommandConv,
    SyncMoveOnCommandConv,
    SyncMoveOffCommandConv,
    EGMStreamConfigCommandConv,
    EGMJointTargetConfigCommandConv,    
    EGMPoseTargetConfigCommandConv,
    EGMPathCorrectionConfigCommandConv,
    EGMRunJointCommandConv,
    EGMRunPoseCommandConv,
    EGMMoveLCommandConv,
    EGMMoveCCommandConv,
    SetWorkObjectCommandConv
]

When implementing a new command, it is best to begin with a FreeformCommand only until the design is complete and verified. Contact the authors before adding a new structure type to any robdef files. Uncoordinated modification of robdef files can cause compatibility issues. Leave the rr_types as an empty list if no specialized structure is available.

Optional Arguments

Some RAPID commands have optional arguments. These can be handled in the following way:

In the Python dataclass for the command, add the parameter as an attribute and set it to None by default. Write a float32 as a flag if the value is set or not.

@dataclass
class WaitDICommand(CommandBase):
    command_opcode = 1001

    signal: str
    value: int
    max_time: float = None

    def write_params(self, f: io.IOBase):
        f.write(util.str_to_bin(signal))
        f.write(util.num_to_bin(value))
        if max_time is not None:
            f.write(util.num_to_bin(1))
            f.write(util.num_to_bin(max_time))
        else:
            f.write(util.num_to_bin(0))
            f.write(util.num_to_bin(0))

Now in the RAPID module, check if the flag is true. If not true, use a default value.

FUNC bool try_motion_program_waitdi(num cmd_num)
    VAR string name;
    VAR signaldi din;
    VAR num value;
    VAR dionum dval;
    VAR num max_time_flag;
    VAR num max_time:= 9e9;
    IF NOT (try_motion_program_read_string(name)
            AND try_motion_program_read_num(value)
            AND try_motion_program_read_num(max_time_flag)
            AND try_motin_program_read_num(max_time) 
    ) THEN
        RETURN FALSE;
    ENDIF
    GetDataVal name, din;
    IF max_time_flag > 0 THEN
        max_time:=max_time;
    ENDIF
    WaitDI din, dval\MaxTime:=max_time;
    RETURN TRUE;
ENDFUNC

In Robot Raconteur, the optional parameters are stored in the "extended" field. All commands (and most other Robot Raconteur structures) are expected to have an "extended" field to capture optional data or for future proofing.

The function cmd_get_extended found in _motion_program_conv.py is used to read extended fields. This function works the same way as cmd_get_arg, but but searches the extended field and returns None if the value is not found. This function can be used to populate the optional value in the Python class.