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:
Directly from the PolyScope X application triggered by user interactions with the contribution presenters.
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:
Robot configuration for the gripper. Configuring the robots TCP, payload, tool voltage and serial interface.
Gripper Program initialization of global variables.
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.