Skip to content

Commit 461fe06

Browse files
committed
Add an example using the move_until_node
1 parent f1640c5 commit 461fe06

File tree

2 files changed

+133
-0
lines changed

2 files changed

+133
-0
lines changed

ur_robot_driver/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,7 @@ install(PROGRAMS
225225
scripts/start_ursim.sh
226226
examples/examples.py
227227
examples/force_mode.py
228+
examples/trajectory_until.py
228229
DESTINATION lib/${PROJECT_NAME}
229230
)
230231

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
#!/usr/bin/env python3
2+
# Copyright 2025, Universal Robots A/S
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 time
31+
32+
import rclpy
33+
from rclpy.action import ActionClient
34+
35+
from builtin_interfaces.msg import Duration
36+
from trajectory_msgs.msg import JointTrajectory, JointTrajectoryPoint
37+
from ur_msgs.action import FollowJointTrajectoryUntil
38+
39+
ROBOT_JOINTS = [
40+
"elbow_joint",
41+
"shoulder_lift_joint",
42+
"shoulder_pan_joint",
43+
"wrist_1_joint",
44+
"wrist_2_joint",
45+
"wrist_3_joint",
46+
]
47+
48+
49+
class MoveUntilExample(rclpy.node.Node):
50+
def __init__(self):
51+
super().__init__("move_until_example")
52+
53+
self._send_goal_future = None
54+
self._get_result_future = None
55+
self._goal_handle = None
56+
self._action_client = ActionClient(
57+
self, FollowJointTrajectoryUntil, "/trajectory_until_node/execute"
58+
)
59+
self._action_client.wait_for_server()
60+
self.test_traj = {
61+
"waypts": [[1.5, -1.5, 0.0, -1.5, -1.5, -1.5], [2.1, -1.2, 0.0, -2.4, -1.5, -1.5]],
62+
"time_vec": [Duration(sec=3, nanosec=0), Duration(sec=6, nanosec=0)],
63+
}
64+
self.get_logger().info("Initialized")
65+
66+
def cancel_goal(self):
67+
if self._goal_handle is not None:
68+
self.get_logger().info("Cancelling goal")
69+
future = self._goal_handle.cancel_goal_async()
70+
future.add_done_callback(self.cancel_done)
71+
72+
def cancel_done(self, future):
73+
try:
74+
future.result()
75+
self.get_logger().info("Goal cancelled successfully")
76+
except Exception as e:
77+
self.get_logger().error(f"Failed to cancel goal: {e}")
78+
79+
def process(self):
80+
trajectory = JointTrajectory()
81+
trajectory.joint_names = ROBOT_JOINTS
82+
83+
trajectory.points = [
84+
JointTrajectoryPoint(
85+
positions=self.test_traj["waypts"][i], time_from_start=self.test_traj["time_vec"][i]
86+
)
87+
for i in range(len(self.test_traj["waypts"]))
88+
]
89+
goal = FollowJointTrajectoryUntil.Goal(
90+
trajectory=trajectory, until_type=FollowJointTrajectoryUntil.Goal.TOOL_CONTACT
91+
)
92+
self._send_goal_future = self._action_client.send_goal_async(goal)
93+
rclpy.spin_until_future_complete(self, self._send_goal_future)
94+
self._goal_handle = self._send_goal_future.result()
95+
if not self._goal_handle.accepted:
96+
self.get_logger().error("Goal rejected :(")
97+
raise RuntimeError("Goal rejected :(")
98+
99+
self.get_logger().info(
100+
f"Goal accepted with ID: {bytes(self._goal_handle.goal_id.uuid).hex()}\n"
101+
)
102+
103+
result_future = self._goal_handle.get_result_async()
104+
rclpy.spin_until_future_complete(self, result_future)
105+
result = result_future.result().result
106+
print(result)
107+
if result is None:
108+
self.get_logger().error("Result is None")
109+
return
110+
if result.error_code != FollowJointTrajectoryUntil.Result.SUCCESSFUL:
111+
self.get_logger().error(f"Result error code: {result.error_code}")
112+
return
113+
if result.until_condition_result == FollowJointTrajectoryUntil.Result.NOT_TRIGGERED:
114+
self.get_logger().info("Trajectory executed without tool contact trigger")
115+
else:
116+
self.get_logger().info("Trajectory executed with tool contact trigger")
117+
118+
self.get_logger().info("Trajectory executed successfully with tool contact condition")
119+
120+
121+
if __name__ == "__main__":
122+
rclpy.init()
123+
124+
node = MoveUntilExample()
125+
try:
126+
node.process()
127+
except KeyboardInterrupt:
128+
node.get_logger().info("Interrupted")
129+
node.cancel_goal()
130+
time.sleep(2)
131+
132+
rclpy.shutdown()

0 commit comments

Comments
 (0)