|
1 | 1 | #!/usr/bin/env python3 |
2 | | -# Copyright 2024, Universal Robots A/S |
| 2 | +# Copyright 2025, Universal Robots A/S |
3 | 3 | # |
4 | 4 | # Redistribution and use in source and binary forms, with or without |
5 | 5 | # modification, are permitted provided that the following conditions are met: |
|
27 | 27 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
28 | 28 | # POSSIBILITY OF SUCH DAMAGE. |
29 | 29 |
|
| 30 | +import sys |
30 | 31 | import time |
31 | 32 |
|
| 33 | +from tf2_ros import TransformException |
| 34 | +from tf2_ros.buffer import Buffer |
| 35 | +from tf2_ros.transform_listener import TransformListener |
| 36 | +import tf2_geometry_msgs # noqa # pylint: disable=unused-import |
| 37 | + |
| 38 | +from builtin_interfaces.msg import Duration |
| 39 | + |
32 | 40 | import rclpy |
| 41 | +from rclpy.callback_groups import MutuallyExclusiveCallbackGroup |
| 42 | +from rclpy.executors import ExternalShutdownException, MultiThreadedExecutor |
33 | 43 | from rclpy.node import Node |
34 | 44 | from controller_manager_msgs.srv import SwitchController |
35 | | -from builtin_interfaces.msg import Duration |
36 | 45 | from geometry_msgs.msg import Twist |
37 | | -from std_msgs.msg import Header |
38 | 46 | from std_srvs.srv import Trigger |
39 | 47 |
|
| 48 | +from visualization_msgs.msg import Marker |
| 49 | + |
40 | 50 | from geometry_msgs.msg import ( |
41 | 51 | Point, |
42 | | - Quaternion, |
43 | | - Pose, |
44 | 52 | PoseStamped, |
45 | 53 | Wrench, |
46 | 54 | Vector3, |
| 55 | + Vector3Stamped, |
47 | 56 | ) |
48 | | - |
49 | 57 | from ur_msgs.srv import SetForceMode |
50 | 58 |
|
51 | 59 | from examples import Robot |
52 | 60 |
|
| 61 | + |
| 62 | +class ForceModeExample(Node): |
| 63 | + def __init__(self): |
| 64 | + super().__init__("force_mode_example") |
| 65 | + |
| 66 | + self.robot = Robot(self) |
| 67 | + # Add force mode service to service interfaces and re-init robot |
| 68 | + self.robot.service_interfaces.update( |
| 69 | + {"/force_mode_controller/start_force_mode": SetForceMode} |
| 70 | + ) |
| 71 | + self.robot.service_interfaces.update({"/force_mode_controller/stop_force_mode": Trigger}) |
| 72 | + self.robot.init_robot() |
| 73 | + |
| 74 | + self.marker_publisher = self.create_publisher(Marker, "direction", 10) |
| 75 | + self.tf_buffer = Buffer() |
| 76 | + self.tf_listener = TransformListener(self.tf_buffer, self) |
| 77 | + |
| 78 | + self.direction = Vector3(x=0.0, y=0.0, z=1.0) |
| 79 | + |
| 80 | + self.marker_published = False |
| 81 | + self.force_mode_start_time = None |
| 82 | + self.startup() |
| 83 | + |
| 84 | + self.timer_cb_group = MutuallyExclusiveCallbackGroup() |
| 85 | + self.timer = self.create_timer(1.0, self.on_timer, self.timer_cb_group) |
| 86 | + |
| 87 | + def startup(self): |
| 88 | + # Press play on the robot |
| 89 | + self.robot.call_service("/dashboard_client/play", Trigger.Request()) |
| 90 | + |
| 91 | + time.sleep(0.5) |
| 92 | + # Start controllers |
| 93 | + self.robot.call_service( |
| 94 | + "/controller_manager/switch_controller", |
| 95 | + SwitchController.Request( |
| 96 | + deactivate_controllers=[ |
| 97 | + "scaled_joint_trajectory_controller", |
| 98 | + "forward_position_controller", |
| 99 | + ], |
| 100 | + activate_controllers=[ |
| 101 | + "passthrough_trajectory_controller", |
| 102 | + "force_mode_controller", |
| 103 | + ], |
| 104 | + strictness=SwitchController.Request.BEST_EFFORT, |
| 105 | + ), |
| 106 | + ) |
| 107 | + self.move_to_starting_pose() |
| 108 | + |
| 109 | + def on_timer(self): |
| 110 | + if not self.marker_published: |
| 111 | + self.publish_direction_marker() |
| 112 | + self.marker_published = True |
| 113 | + elif self.force_mode_start_time is None: |
| 114 | + self.start_force_mode() |
| 115 | + self.force_mode_start_time = self.get_clock().now() |
| 116 | + self.get_logger().info("Force mode started.") |
| 117 | + elif (self.get_clock().now() - self.force_mode_start_time).nanoseconds > 3e9: |
| 118 | + self.get_logger().info("Stopping force mode after 3 seconds.") |
| 119 | + self.robot.call_service("/force_mode_controller/stop_force_mode", Trigger.Request()) |
| 120 | + sys.exit(0) |
| 121 | + |
| 122 | + def move_to_starting_pose(self): |
| 123 | + # Move robot in to position |
| 124 | + self.robot.send_trajectory( |
| 125 | + waypts=[[-1.6, -1.55, -1.7, -1.0, 2.05, 0.5]], |
| 126 | + time_vec=[Duration(sec=5, nanosec=0)], |
| 127 | + action_client=self.robot.passthrough_trajectory_action_client, |
| 128 | + ) |
| 129 | + |
| 130 | + def start_force_mode(self): |
| 131 | + # Create task frame for force mode |
| 132 | + frame_stamp = PoseStamped() |
| 133 | + frame_stamp.header.frame_id = "tool0_controller" |
| 134 | + frame_stamp.pose.position.x = 0.0 |
| 135 | + frame_stamp.pose.position.y = 0.0 |
| 136 | + frame_stamp.pose.position.z = 0.0 |
| 137 | + frame_stamp.pose.orientation.x = 0.0 |
| 138 | + frame_stamp.pose.orientation.y = 0.0 |
| 139 | + frame_stamp.pose.orientation.z = 0.0 |
| 140 | + frame_stamp.pose.orientation.w = 1.0 |
| 141 | + |
| 142 | + wrench_vec = Wrench( |
| 143 | + force=Vector3(x=0.0, y=0.0, z=10.0), torque=Vector3(x=0.0, y=0.0, z=0.0) |
| 144 | + ) |
| 145 | + type_spec = SetForceMode.Request.NO_TRANSFORM |
| 146 | + |
| 147 | + # Specify max speeds and deviations of force mode |
| 148 | + speed_limits = Twist() |
| 149 | + speed_limits.linear = Vector3(x=0.0, y=0.0, z=1.0) |
| 150 | + speed_limits.angular = Vector3(x=0.0, y=0.0, z=0.0) |
| 151 | + deviation_limits = [0.005, 0.005, 0.005, 0.005, 0.005, 0.005] |
| 152 | + |
| 153 | + # specify damping and gain scaling |
| 154 | + damping_factor = 0.1 |
| 155 | + gain_scale = 0.8 |
| 156 | + |
| 157 | + req = SetForceMode.Request() |
| 158 | + req.task_frame = frame_stamp |
| 159 | + req.selection_vector_x = False |
| 160 | + req.selection_vector_y = False |
| 161 | + req.selection_vector_z = True |
| 162 | + req.selection_vector_rx = False |
| 163 | + req.selection_vector_ry = False |
| 164 | + req.selection_vector_rz = False |
| 165 | + req.wrench = wrench_vec |
| 166 | + req.type = type_spec |
| 167 | + req.speed_limits = speed_limits |
| 168 | + req.deviation_limits = deviation_limits |
| 169 | + req.damping_factor = damping_factor |
| 170 | + req.gain_scaling = gain_scale |
| 171 | + |
| 172 | + self.get_logger().info(f"Starting force mode with {req}") |
| 173 | + self.robot.call_service("/force_mode_controller/start_force_mode", req) |
| 174 | + |
| 175 | + def publish_direction_marker(self): |
| 176 | + """Publish a line strip going from the current TCP position to the desired direction.""" |
| 177 | + to_frame_rel = "base" |
| 178 | + from_frame_rel = "tool0_controller" |
| 179 | + try: |
| 180 | + t = self.tf_buffer.lookup_transform( |
| 181 | + to_frame_rel, |
| 182 | + from_frame_rel, |
| 183 | + rclpy.time.Time(), |
| 184 | + timeout=rclpy.time.Duration(seconds=10.0), |
| 185 | + ) |
| 186 | + self.get_logger().info( |
| 187 | + f"[{t.transform.translation.x}, {t.transform.translation.y}, {t.transform.translation.z}]" |
| 188 | + ) |
| 189 | + direction_vec = Vector3Stamped() |
| 190 | + direction_vec.header.frame_id = from_frame_rel |
| 191 | + direction_vec.header.stamp = rclpy.time.Time() |
| 192 | + direction_vec.vector = self.direction |
| 193 | + transformed_direction = self.tf_buffer.transform( |
| 194 | + direction_vec, to_frame_rel, timeout=rclpy.time.Duration(seconds=1.0) |
| 195 | + ) |
| 196 | + except TransformException as ex: |
| 197 | + self.get_logger().info(f"Could not transform {to_frame_rel} to {from_frame_rel}: {ex}") |
| 198 | + return |
| 199 | + |
| 200 | + marker = Marker() |
| 201 | + marker.header.frame_id = "base" |
| 202 | + marker.header.stamp = self.get_clock().now().to_msg() |
| 203 | + marker.type = marker.ARROW |
| 204 | + marker.id = 0 |
| 205 | + marker.action = marker.ADD |
| 206 | + marker.scale.x = 0.01 |
| 207 | + marker.scale.y = 0.02 |
| 208 | + marker.color.r = 1.0 |
| 209 | + marker.color.g = 0.0 |
| 210 | + marker.color.b = 0.0 |
| 211 | + marker.color.a = 1.0 |
| 212 | + marker.points.append( |
| 213 | + Point( |
| 214 | + x=t.transform.translation.x, |
| 215 | + y=t.transform.translation.y, |
| 216 | + z=t.transform.translation.z, |
| 217 | + ) |
| 218 | + ) |
| 219 | + marker.points.append( |
| 220 | + Point( |
| 221 | + x=t.transform.translation.x + transformed_direction.vector.x, |
| 222 | + y=t.transform.translation.y + transformed_direction.vector.y, |
| 223 | + z=t.transform.translation.z + transformed_direction.vector.z, |
| 224 | + ) |
| 225 | + ) |
| 226 | + self.marker_publisher.publish(marker) |
| 227 | + |
| 228 | + |
53 | 229 | if __name__ == "__main__": |
54 | 230 | rclpy.init() |
55 | | - node = Node("robot_driver_test") |
56 | | - robot = Robot(node) |
57 | | - |
58 | | - # Add force mode service to service interfaces and re-init robot |
59 | | - robot.service_interfaces.update({"/force_mode_controller/start_force_mode": SetForceMode}) |
60 | | - robot.service_interfaces.update({"/force_mode_controller/stop_force_mode": Trigger}) |
61 | | - robot.init_robot() |
62 | | - time.sleep(0.5) |
63 | | - # Press play on the robot |
64 | | - robot.call_service("/dashboard_client/play", Trigger.Request()) |
65 | | - |
66 | | - time.sleep(0.5) |
67 | | - # Start controllers |
68 | | - robot.call_service( |
69 | | - "/controller_manager/switch_controller", |
70 | | - SwitchController.Request( |
71 | | - deactivate_controllers=["scaled_joint_trajectory_controller"], |
72 | | - activate_controllers=["passthrough_trajectory_controller", "force_mode_controller"], |
73 | | - strictness=SwitchController.Request.BEST_EFFORT, |
74 | | - ), |
75 | | - ) |
76 | | - |
77 | | - # Move robot in to position |
78 | | - robot.send_trajectory( |
79 | | - waypts=[[-1.5707, -1.5707, -1.5707, -1.5707, 1.5707, 0]], |
80 | | - time_vec=[Duration(sec=5, nanosec=0)], |
81 | | - action_client=robot.passthrough_trajectory_action_client, |
82 | | - ) |
83 | | - |
84 | | - # Finished moving |
85 | | - # Create task frame for force mode |
86 | | - point = Point(x=0.0, y=0.0, z=0.0) |
87 | | - orientation = Quaternion(x=0.0, y=0.0, z=0.0, w=1.0) |
88 | | - task_frame_pose = Pose() |
89 | | - task_frame_pose.position = point |
90 | | - task_frame_pose.orientation = orientation |
91 | | - header = Header(seq=1, frame_id="world") |
92 | | - header.stamp.sec = int(time.time()) + 1 |
93 | | - header.stamp.nanosec = 0 |
94 | | - frame_stamp = PoseStamped() |
95 | | - frame_stamp.header = header |
96 | | - frame_stamp.pose = task_frame_pose |
97 | | - |
98 | | - # Create compliance vector (which axes should be force controlled) |
99 | | - compliance = [False, False, True, False, False, False] |
100 | | - |
101 | | - # Create Wrench message for force mode |
102 | | - wrench_vec = Wrench(force=Vector3(x=0.0, y=0.0, z=-20.0), torque=Vector3(x=0.0, y=0.0, z=0.0)) |
103 | | - # Specify interpretation of task frame (no transform) |
104 | | - type_spec = SetForceMode.Request.NO_TRANSFORM |
105 | | - |
106 | | - # Specify max speeds and deviations of force mode |
107 | | - speed_limits = Twist() |
108 | | - speed_limits.linear = Vector3(x=0.0, y=0.0, z=1.0) |
109 | | - speed_limits.angular = Vector3(x=0.0, y=0.0, z=1.0) |
110 | | - deviation_limits = [1.0, 1.0, 1.0, 1.0, 1.0, 1.0] |
111 | | - |
112 | | - # specify damping and gain scaling |
113 | | - damping_factor = 0.1 |
114 | | - gain_scale = 0.8 |
115 | | - |
116 | | - req = SetForceMode.Request() |
117 | | - req.task_frame = frame_stamp |
118 | | - req.selection_vector_x = compliance[0] |
119 | | - req.selection_vector_y = compliance[1] |
120 | | - req.selection_vector_z = compliance[2] |
121 | | - req.selection_vector_rx = compliance[3] |
122 | | - req.selection_vector_ry = compliance[4] |
123 | | - req.selection_vector_rz = compliance[5] |
124 | | - req.wrench = wrench_vec |
125 | | - req.type = type_spec |
126 | | - req.speed_limits = speed_limits |
127 | | - req.deviation_limits = deviation_limits |
128 | | - req.damping_factor = damping_factor |
129 | | - req.gain_scaling = gain_scale |
130 | | - |
131 | | - # Send request to controller |
132 | | - node.get_logger().info(f"Starting force mode with {req}") |
133 | | - robot.call_service("/force_mode_controller/start_force_mode", req) |
134 | | - robot.send_trajectory( |
135 | | - waypts=[[1.5707, -1.5707, -1.5707, -1.5707, 1.5707, 0]], |
136 | | - time_vec=[Duration(sec=5, nanosec=0)], |
137 | | - action_client=robot.passthrough_trajectory_action_client, |
138 | | - ) |
139 | | - |
140 | | - time.sleep(3) |
141 | | - node.get_logger().info("Deactivating force mode controller.") |
142 | | - robot.call_service("/force_mode_controller/stop_force_mode", Trigger.Request()) |
| 231 | + |
| 232 | + node = ForceModeExample() |
| 233 | + executor = MultiThreadedExecutor() |
| 234 | + executor.add_node(node) |
| 235 | + try: |
| 236 | + rclpy.spin(node) |
| 237 | + except (KeyboardInterrupt, ExternalShutdownException): |
| 238 | + pass |
| 239 | + |
| 240 | + rclpy.shutdown() |
| 241 | + |
| 242 | + # time.sleep(0.5) |
| 243 | + |
| 244 | + # # Send request to controller |
0 commit comments