Skip to content

Commit 34e17a3

Browse files
authored
[force mode controller] Fix the task frame orientation (backport of #1379) (#1381)
* [force mode controller] Fix the task frame orientation Before, we were using a RPY notation. Chaning that to an angle-axis notation should fix things. * Update force_mode example to include a pose that is not in line with one of the main axes * Make test use an arbitraty orientation This should avoid a coincitental overlap if the transformation is wrong.
1 parent 322e3c7 commit 34e17a3

File tree

3 files changed

+277
-110
lines changed

3 files changed

+277
-110
lines changed

ur_controllers/src/force_mode_controller.cpp

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -289,9 +289,11 @@ bool ForceModeController::setForceMode(const ur_msgs::srv::SetForceMode::Request
289289

290290
tf2::Quaternion quat_tf;
291291
tf2::convert(task_frame_transformed.pose.orientation, quat_tf);
292-
tf2::Matrix3x3 rot_mat(quat_tf);
293-
rot_mat.getRPY(force_mode_parameters.task_frame[3], force_mode_parameters.task_frame[4],
294-
force_mode_parameters.task_frame[5]);
292+
const double angle = quat_tf.getAngle();
293+
const auto axis = quat_tf.getAxis();
294+
force_mode_parameters.task_frame[3] = axis.x() * angle; // rx
295+
force_mode_parameters.task_frame[4] = axis.y() * angle; // ry
296+
force_mode_parameters.task_frame[5] = axis.z() * angle; // rz
295297
} catch (const tf2::TransformException& ex) {
296298
RCLCPP_ERROR(get_node()->get_logger(), "Could not transform %s to robot base: %s",
297299
req->task_frame.header.frame_id.c_str(), ex.what());
Lines changed: 196 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#!/usr/bin/env python3
2-
# Copyright 2024, Universal Robots A/S
2+
# Copyright 2025, Universal Robots A/S
33
#
44
# Redistribution and use in source and binary forms, with or without
55
# modification, are permitted provided that the following conditions are met:
@@ -27,116 +27,218 @@
2727
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
2828
# POSSIBILITY OF SUCH DAMAGE.
2929

30+
import sys
3031
import time
3132

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+
3240
import rclpy
41+
from rclpy.callback_groups import MutuallyExclusiveCallbackGroup
42+
from rclpy.executors import ExternalShutdownException, MultiThreadedExecutor
3343
from rclpy.node import Node
3444
from controller_manager_msgs.srv import SwitchController
35-
from builtin_interfaces.msg import Duration
3645
from geometry_msgs.msg import Twist
37-
from std_msgs.msg import Header
3846
from std_srvs.srv import Trigger
3947

48+
from visualization_msgs.msg import Marker
49+
4050
from geometry_msgs.msg import (
4151
Point,
42-
Quaternion,
43-
Pose,
4452
PoseStamped,
4553
Wrench,
4654
Vector3,
55+
Vector3Stamped,
4756
)
48-
4957
from ur_msgs.srv import SetForceMode
5058

5159
from examples import Robot
5260

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+
53229
if __name__ == "__main__":
54230
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

Comments
 (0)