Skip to content

Commit 27c9a9c

Browse files
author
Felix Exner (fexner)
authored
[doc] Add more documentation regarding usage (#1055)
* Add note about installing ros2controlcli * Add example_move script * Add test for example_move
1 parent ec79784 commit 27c9a9c

File tree

4 files changed

+298
-2
lines changed

4 files changed

+298
-2
lines changed

ur_robot_driver/CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ ament_python_install_package(${PROJECT_NAME})
146146
install(PROGRAMS
147147
scripts/tool_communication.py
148148
scripts/wait_for_robot_description
149+
scripts/example_move.py
149150
scripts/start_ursim.sh
150151
DESTINATION lib/${PROJECT_NAME}
151152
)
@@ -171,6 +172,10 @@ if(BUILD_TESTING)
171172
TIMEOUT
172173
180
173174
)
175+
add_launch_test(test/example_move.py
176+
TIMEOUT
177+
180
178+
)
174179
add_launch_test(test/robot_driver.py
175180
TIMEOUT
176181
800

ur_robot_driver/doc/usage.rst

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ The most relevant arguments are the following:
5353

5454
Note: *joint_state_broadcaster*\ , *speed_scaling_state_broadcaster*\ , *force_torque_sensor_broadcaster*\ , and *io_and_status_controller* will always start.
5555

56-
*HINT* : list all loaded controllers using ``ros2 control list_controllers`` command.
56+
*HINT* : list all loaded controllers using ``ros2 control list_controllers`` command. For this,
57+
the package ``ros2controlcli`` must be installed (``sudo apt-get install ros-${ROS_DISTRO}-ros2controlcli``).
5758

5859
**NOTE**\ : The package can simulate hardware with the ros2_control ``MockSystem``. This emulator enables an environment for testing of "piping" of hardware and controllers, as well as testing robot's descriptions. For more details see `ros2_control documentation <https://ros-controls.github.io/control.ros.org/>`_ for more details.
5960

@@ -139,7 +140,7 @@ Allowed UR - Type strings: ``ur3``\ , ``ur3e``\ , ``ur5``\ , ``ur5e``\ , ``ur10`
139140
2. Sending commands to controllers
140141
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
141142

142-
Before running any commands, first check the controllers' state using ``ros2 control list_controllers``.
143+
Before running any commands, first check the controllers' state using ``ros2 control list_controllers`` (Remember to install the ``ros2controlcli`` package as mentioned above).
143144

144145

145146
* Send some goal to the Joint Trajectory Controller by using a demo node from `ros2_controllers_test_nodes <https://github.com/ros-controls/ros2_controllers/blob/master/ros2_controllers_test_nodes/ros2_controllers_test_nodes/publisher_joint_trajectory_controller.py>`_ package by starting the following command in another terminal:
@@ -164,6 +165,28 @@ Before running any commands, first check the controllers' state using ``ros2 con
164165
165166
After a few seconds the robot should move(or jump when using emulation).
166167

168+
In case you want to write your own ROS node to move the robot, there is an example python node included that you can use as a start.
169+
170+
171+
.. code-block:: console
172+
173+
$ ros2 run ur_robot_driver example_move.py
174+
[INFO] [1720623611.547903428] [jtc_client]: Waiting for action server on scaled_joint_trajectory_controller/follow_joint_trajectory
175+
[INFO] [1720623611.548368095] [jtc_client]: Executing trajectory traj0
176+
[INFO] [1720623620.530203889] [jtc_client]: Done with result: SUCCESSFUL
177+
[INFO] [1720623622.530668700] [jtc_client]: Executing trajectory traj1
178+
[INFO] [1720623630.582108072] [jtc_client]: Done with result: SUCCESSFUL
179+
[INFO] [1720623632.582576444] [jtc_client]: Done with all trajectories
180+
[INFO] [1720623632.582957452] [jtc_client]: Done
181+
182+
183+
.. warning::
184+
185+
This is a very basic node that doesn't have the same safety checks as the test nodes above. Look
186+
at the code and make sure that the robot is able to perform the motions safely before running
187+
this on a real robot!
188+
189+
167190
3. Using only robot description
168191
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
169192

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
#!/usr/bin/env python3
2+
# Copyright (c) 2024 FZI Forschungszentrum Informatik
3+
#
4+
# Redistribution and use in source and binary forms, with or without
5+
# modification, are permitted provided that the following conditions are met:
6+
#
7+
# * Redistributions of source code must retain the above copyright
8+
# notice, this list of conditions and the following disclaimer.
9+
#
10+
# * Redistributions in binary form must reproduce the above copyright
11+
# notice, this list of conditions and the following disclaimer in the
12+
# documentation and/or other materials provided with the distribution.
13+
#
14+
# * Neither the name of the {copyright_holder} nor the names of its
15+
# contributors may be used to endorse or promote products derived from
16+
# this software without specific prior written permission.
17+
#
18+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
22+
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23+
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24+
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27+
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28+
# POSSIBILITY OF SUCH DAMAGE.
29+
30+
#
31+
# Author: Felix Exner
32+
33+
# This is an example of how to interface the robot without any additional ROS components. For
34+
# real-life applications, we do recommend to use something like MoveIt!
35+
36+
import time
37+
38+
import rclpy
39+
from rclpy.action import ActionClient
40+
41+
from builtin_interfaces.msg import Duration
42+
from trajectory_msgs.msg import JointTrajectory, JointTrajectoryPoint
43+
from control_msgs.action import FollowJointTrajectory
44+
45+
TRAJECTORIES = {
46+
"traj0": [
47+
{
48+
"positions": [0.043128, -1.28824, 1.37179, -1.82208, -1.63632, -0.18],
49+
"velocities": [0, 0, 0, 0, 0, 0],
50+
"time_from_start": Duration(sec=4, nanosec=0),
51+
},
52+
{
53+
"positions": [-0.195016, -1.70093, 0.902027, -0.944217, -1.52982, -0.195171],
54+
"velocities": [0, 0, 0, 0, 0, 0],
55+
"time_from_start": Duration(sec=8, nanosec=0),
56+
},
57+
],
58+
"traj1": [
59+
{
60+
"positions": [-0.195016, -1.70094, 0.902027, -0.944217, -1.52982, -0.195171],
61+
"velocities": [0, 0, 0, 0, 0, 0],
62+
"time_from_start": Duration(sec=0, nanosec=0),
63+
},
64+
{
65+
"positions": [0.30493, -0.982258, 0.955637, -1.48215, -1.72737, 0.204445],
66+
"velocities": [0, 0, 0, 0, 0, 0],
67+
"time_from_start": Duration(sec=8, nanosec=0),
68+
},
69+
],
70+
}
71+
72+
73+
class JTCClient(rclpy.node.Node):
74+
"""Small test client for the jtc."""
75+
76+
def __init__(self):
77+
super().__init__("jtc_client")
78+
self.declare_parameter("controller_name", "scaled_joint_trajectory_controller")
79+
self.declare_parameter(
80+
"joints",
81+
[
82+
"shoulder_pan_joint",
83+
"shoulder_lift_joint",
84+
"elbow_joint",
85+
"wrist_1_joint",
86+
"wrist_2_joint",
87+
"wrist_3_joint",
88+
],
89+
)
90+
91+
controller_name = self.get_parameter("controller_name").value + "/follow_joint_trajectory"
92+
self.joints = self.get_parameter("joints").value
93+
94+
if self.joints is None or len(self.joints) == 0:
95+
raise Exception('"joints" parameter is required')
96+
97+
self._action_client = ActionClient(self, FollowJointTrajectory, controller_name)
98+
self.get_logger().info(f"Waiting for action server on {controller_name}")
99+
self._action_client.wait_for_server()
100+
101+
self.parse_trajectories()
102+
self.i = 0
103+
self._send_goal_future = None
104+
self._get_result_future = None
105+
self.execute_next_trajectory()
106+
107+
def parse_trajectories(self):
108+
self.goals = {}
109+
110+
for traj_name in TRAJECTORIES:
111+
goal = JointTrajectory()
112+
goal.joint_names = self.joints
113+
for pt in TRAJECTORIES[traj_name]:
114+
point = JointTrajectoryPoint()
115+
point.positions = pt["positions"]
116+
point.velocities = pt["velocities"]
117+
point.time_from_start = pt["time_from_start"]
118+
goal.points.append(point)
119+
120+
self.goals[traj_name] = goal
121+
122+
def execute_next_trajectory(self):
123+
if self.i >= len(self.goals):
124+
self.get_logger().info("Done with all trajectories")
125+
raise SystemExit
126+
traj_name = list(self.goals)[self.i]
127+
self.i = self.i + 1
128+
if traj_name:
129+
self.execute_trajectory(traj_name)
130+
131+
def execute_trajectory(self, traj_name):
132+
self.get_logger().info(f"Executing trajectory {traj_name}")
133+
goal = FollowJointTrajectory.Goal()
134+
goal.trajectory = self.goals[traj_name]
135+
136+
goal.goal_time_tolerance = Duration(sec=0, nanosec=1000)
137+
138+
self._send_goal_future = self._action_client.send_goal_async(goal)
139+
self._send_goal_future.add_done_callback(self.goal_response_callback)
140+
141+
def goal_response_callback(self, future):
142+
goal_handle = future.result()
143+
if not goal_handle.accepted:
144+
self.get_logger().error("Goal rejected :(")
145+
raise RuntimeError("Goal rejected :(")
146+
147+
self.get_logger().debug("Goal accepted :)")
148+
149+
self._get_result_future = goal_handle.get_result_async()
150+
self._get_result_future.add_done_callback(self.get_result_callback)
151+
152+
def get_result_callback(self, future):
153+
result = future.result().result
154+
self.get_logger().info(f"Done with result: {self.error_code_to_str(result.error_code)}")
155+
if result.error_code == FollowJointTrajectory.Result.SUCCESSFUL:
156+
time.sleep(2)
157+
self.execute_next_trajectory()
158+
else:
159+
raise RuntimeError("Executing trajectory failed")
160+
161+
@staticmethod
162+
def error_code_to_str(error_code):
163+
if error_code == FollowJointTrajectory.Result.SUCCESSFUL:
164+
return "SUCCESSFUL"
165+
if error_code == FollowJointTrajectory.Result.INVALID_GOAL:
166+
return "INVALID_GOAL"
167+
if error_code == FollowJointTrajectory.Result.INVALID_JOINTS:
168+
return "INVALID_JOINTS"
169+
if error_code == FollowJointTrajectory.Result.OLD_HEADER_TIMESTAMP:
170+
return "OLD_HEADER_TIMESTAMP"
171+
if error_code == FollowJointTrajectory.Result.PATH_TOLERANCE_VIOLATED:
172+
return "PATH_TOLERANCE_VIOLATED"
173+
if error_code == FollowJointTrajectory.Result.GOAL_TOLERANCE_VIOLATED:
174+
return "GOAL_TOLERANCE_VIOLATED"
175+
176+
177+
def main(args=None):
178+
rclpy.init(args=args)
179+
180+
jtc_client = JTCClient()
181+
try:
182+
rclpy.spin(jtc_client)
183+
except SystemExit:
184+
rclpy.logging.get_logger("jtc_client").info("Done")
185+
186+
rclpy.shutdown()
187+
188+
189+
if __name__ == "__main__":
190+
main()
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
#!/usr/bin/env python
2+
# Copyright 2019, FZI Forschungszentrum Informatik
3+
#
4+
# Redistribution and use in source and binary forms, with or without
5+
# modification, are permitted provided that the following conditions are met:
6+
#
7+
# * Redistributions of source code must retain the above copyright
8+
# notice, this list of conditions and the following disclaimer.
9+
#
10+
# * Redistributions in binary form must reproduce the above copyright
11+
# notice, this list of conditions and the following disclaimer in the
12+
# documentation and/or other materials provided with the distribution.
13+
#
14+
# * Neither the name of the {copyright_holder} nor the names of its
15+
# contributors may be used to endorse or promote products derived from
16+
# this software without specific prior written permission.
17+
#
18+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
22+
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23+
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24+
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27+
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28+
# POSSIBILITY OF SUCH DAMAGE.
29+
30+
import os
31+
import subprocess
32+
import sys
33+
import time
34+
import unittest
35+
36+
import rclpy
37+
from rclpy.node import Node as ROSNode
38+
39+
sys.path.append(os.path.dirname(__file__))
40+
from test_common import ( # noqa: E402
41+
generate_driver_test_description,
42+
DashboardInterface,
43+
IoStatusInterface,
44+
)
45+
46+
47+
def generate_test_description():
48+
ld = generate_driver_test_description()
49+
return ld
50+
51+
52+
class ExampleMoveTest(unittest.TestCase):
53+
@classmethod
54+
def setUpClass(cls):
55+
# Initialize the ROS context
56+
rclpy.init()
57+
cls.node = ROSNode("example_move_test")
58+
time.sleep(1)
59+
cls.init_robot(cls)
60+
61+
@classmethod
62+
def tearDownClass(cls):
63+
# Shutdown the ROS context
64+
cls.node.destroy_node()
65+
rclpy.shutdown()
66+
67+
def init_robot(self):
68+
self._dashboard_interface = DashboardInterface(self.node)
69+
self._io_status_controller_interface = IoStatusInterface(self.node)
70+
71+
def setUp(self):
72+
self._dashboard_interface.start_robot()
73+
time.sleep(1)
74+
self.assertTrue(self._io_status_controller_interface.resend_robot_program().success)
75+
76+
def test_example_move(self):
77+
cmd = ["ros2", "run", "ur_robot_driver", "example_move.py"]
78+
subprocess.check_output(cmd)

0 commit comments

Comments
 (0)