Creating a gripper contribution

A gripper contribution is a URCap that enables a device attached to the robots tool flange and is connected to the robot using the tool serial interface.

A gripper contribution can be made using a ROS2 backend contribution and a Frontend contribution.

The backend contribution will take care of all communication with the physical gripper. This way all logic and protocols associated with specific gripper can be isolated in the backend. The backend will also need to expose an interface that allows the application to control the gripper. In the gripper sample provided we use ROS2 for the control interface.

The frontend contribution(s) will be the client to the backend contribution either by calling the control interface directly from the application or by generating script that will use the backend contribution at program runtime

Release 0.9-alpha limitations

Limitation

Workaround

URScript does not support ROS2 services. See ROS and struct script manual entry

Use ROS2 topics in URScript

Not possible to contribute custom ROS types

Fit ROS2 data in existing services and messages. URinterfaces, ROS2 std_msgs, ROS2 rcl_interfaces

Take care when generating URscript from application nodes (preamble). The preamble is called both when the robot is started and when running a program. Program specific code should not be run directly in the preamble, only robot configuration script. For other preamble URscript limitations see scriptmanual section 11.1.

Put program specific preamble code in URscript functions called by the program at runtime (See gripper sample)

It is only possible to connect external devices using the tool serial interface og the builtin IO’s

URCap structure

Backend contribution

The gripper backend is contributed in a docker container. The actual contribution is a docker image. The following show how the sample gripper backend is configured in the URCap descriptor manifest.yaml.

....
containers:
    - id: simple-gripper-backend
      image: simple-gripper-backend
      mounts:
        - mount: tmpfs:/root/.ros
          access: rw
      devices:
        - type: ttyTool
...

The “devices” section makes sure that the tool serial interface “/dev/ur-ttylink/ttytool” is available in the docker container.

Frontend contribution

The sample gripper contribution includes 2 standard frontend nodes, an application node and a program node.

The frontend nodes communicates with the gripper backend in 2 ways:

  1. Directly from the PolyScope X application triggered by user interactions with the contribution presenters.

  2. By generating URscript that will communicate with the gripper backend at program runtime.

The frontend contributions are configured in the URCap descriptor manifest.yaml as any other frontend contribution. See How to configure and package a contribution.

Gripper sample

Basic ROS2 frontend and backend contributions are described in How to create a ROS2 backend contribution and How to work with ROS2 in a frontend contribution.

This section describes some gripper specifics added to a standard ROS2 URCap.

Backend contribution

Serial interface

To ease URCap development we make sure that the URCap can run in the simulator event though the serial interface is not available in simulation.

#   /dev/ur-ttylink/ttyTool is the serial path given to the serial connector close to the tool flange
GRIPPER_SERIAL_PATH = "/dev/ur-ttylink/ttyTool"

#   If the URCap is being tested in simulator a simulated serial is imported and used
if os.path.exists(GRIPPER_SERIAL_PATH):
    from simple_gripper.serializer import serial_write, init_serial_communication
else:
    from simple_gripper.serializer_sim import serial_write, init_serial_communication

The “simple_gripper.serializer_sim” will write the serial communication to a log for debug purposes.

ROS2 Topics and services

The sample exposes the following ROS2 interface:

ROS interface

name

Type

Response

Description

Publish Topic

/status

std_msg.msg/String

Grippers current state

Expose Service

/set_force

rcl_interfaces.srv/SetParameters

rcl_interfaces.msg/SetParametersResult

Configure the gripping force

Subscribe Topic

/open_close

std_msg.msg/String

Gripper action

Expose Service

/open_close

rcl_interfaces.srv/SetParameters

rcl_interfaces.msg/SetParametersResult

Gripper action

Duplicate /open_close topic and service

The open_close topic and service have duplicate functionality in the backend contribution. The reason is that we want it to be possible to control the gripper both from script and from the PolyScope X application. Since ROS2 services is not supported in URscript yet, we use this “topic workaround” in script.

ROS2 service types

At this point custom service types are not supported, this is why we use the service types SetParameters/SetParametersResult even though these types are not intended for this kind of communication.

Frontend contributions

The gripper sample includes both an application node and a program node. Both generates URscript and communicates directly with the gripper backend.

Communication with the gripper backend

Both the application node and program node uses the utility functions in RosHelper.ts to handle the ROS2 communication with the backend. The RosHelper uses the ROS2Client API for the ROS2 communication see How to work with ROS2 in a frontend contribution.

The Frontend calls the two services exposed by the gripper backend:

  • /set_force

  • /open_close

The service parameters are fitted in the SetParameters ROS2 type. The RosHelper uses the SetParameters - typescript interface IRosTypeRclInterfacesSetParametersRequest found in the lib “@universal-robots/contribution-api”. All service return values are ignored in the sample.

The frontend subscribes to the /state topic published by the gripper backend. The gripper state value is used in the application presenter to visualize the gripper state and in URscript to determine if a gripper action succeeds.

URscript generation

The sample application node generates preamble URscript for 3 purposes:

  1. Robot configuration for the gripper. Configuring the robots TCP, payload, tool voltage and serial interface.

  2. Gripper Program initialization of global variables.

  3. Global functions available for the program

Example from the Gripper sample:

# Robot Configuration
set_target_payload(5, [0, 0, 0])
set_tcp(p[0, 0, 100, 0, 0, 0])
set_tool_voltage(24)
set_tool_communication(True, 1000000, 2, 1, 1.5, 3.5)

# Program specific variables and functions
global ur_sg_is_gripper_initialized = False
global ur_sg_gripper_status = "N/A"
global ur_sg_step_time = get_steptime()

def ur_sg_init_gripper():
    # Lazy initialization of the gripper, to be called once when the first gripper program-node is executed
    if ur_sg_is_gripper_initialized == False:
        ur_sg_is_gripper_initialized = True
        global ur_sg_force_pub = ${await generatePublisherCode(ROS_SET_FORCE, 'std_msgs/msg/Int64', ros2Client)}
        global ur_sg_action_pub = ${await generatePublisherCode(ROS_OPEN_CLOSE, 'std_msgs/msg/String', ros2Client)}
        global ur_sg_status_sub = ${await generateSubscriberCode(ROS_STATUS, 'std_msgs/msg/String', ros2Client)}

def ur_sg_open_close(action="CLOSE", blocking=True):

The robot configuration is run directly in the preamble, because it should be executed both at robot startup and when running a program. Note that because the gripper sample makes use of the tool serial interface (TCI) it must be enabled and configured in the preamble using the script command “set_tool_communication(True, 1000000, 2, 1, 1.5, 3.5)” see scriptmanual.

Initialization of global variables should only be run when the program is running, never at startup. For that reason all global variable initialization is put in a function (ur_sg_init_gripper) that will be called at program runtime.

The variable “is_gripper_initialized” make sure the initialization is completed only once during program execution.

The central function generated in the preamble is “ur_sg_open_close”. This function takes as parameters the gripper action (open or close) and a parameter for setting the function blocking or nonblocking. When the function is blocking it will not return until it either reads the expected gripper state change or times out after 3 seconds. The function caller can read the final gripper state from “ur_sg_gripper_status” and can use that state to determine if the grip-function succeeded or failed. In non-blocking mode “ur_sg_gripper_status” is not guarantied to be up-to-date immediately and cannot safely be used to determine success or failure.

The gripper program node will generate URscript for opening or closing depending on the program node state set by the user through the program node presenter. If the user have configured the program node as a blocking “close” node, he will also have the possibility to enter URscript code to be executed in case of a close - success and failure. The generated URscript will check if the success/failure functions have been defined, if so, the returned gripper status is used to determine which one should be called.