Skip to content

Commit 255b192

Browse files
committed
Move force_mode_controller integration test to separate file
1 parent efbb144 commit 255b192

File tree

3 files changed

+260
-118
lines changed

3 files changed

+260
-118
lines changed

ur_robot_driver/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,10 @@ if(BUILD_TESTING)
192192
TIMEOUT
193193
800
194194
)
195+
add_launch_test(test/integration_test_force_mode.py
196+
TIMEOUT
197+
800
198+
)
195199
add_launch_test(test/urscript_interface.py
196200
TIMEOUT
197201
500
Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
#!/usr/bin/env python
2+
# Copyright 2019, 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 copy
31+
import logging
32+
import os
33+
import sys
34+
import time
35+
import unittest
36+
37+
import pytest
38+
39+
import launch_testing
40+
import rclpy
41+
from rclpy.node import Node
42+
43+
from tf2_ros import TransformException
44+
from tf2_ros.buffer import Buffer
45+
from tf2_ros.transform_listener import TransformListener
46+
47+
import std_msgs
48+
from controller_manager_msgs.srv import SwitchController
49+
from geometry_msgs.msg import (
50+
Pose,
51+
PoseStamped,
52+
Quaternion,
53+
Point,
54+
Twist,
55+
WrenchStamped,
56+
Wrench,
57+
Vector3,
58+
)
59+
from ur_msgs.srv import SetForceMode
60+
61+
sys.path.append(os.path.dirname(__file__))
62+
from test_common import ( # noqa: E402
63+
ActionInterface,
64+
ControllerManagerInterface,
65+
DashboardInterface,
66+
ForceModeInterface,
67+
IoStatusInterface,
68+
ConfigurationInterface,
69+
generate_driver_test_description,
70+
ROBOT_JOINTS,
71+
)
72+
73+
TIMEOUT_EXECUTE_TRAJECTORY = 30
74+
75+
76+
@pytest.mark.launch_test
77+
@launch_testing.parametrize(
78+
"tf_prefix",
79+
[(""), ("my_ur_")],
80+
)
81+
def generate_test_description(tf_prefix):
82+
return generate_driver_test_description(tf_prefix=tf_prefix)
83+
84+
85+
class RobotDriverTest(unittest.TestCase):
86+
@classmethod
87+
def setUpClass(cls):
88+
# Initialize the ROS context
89+
rclpy.init()
90+
cls.node = Node("robot_driver_test")
91+
time.sleep(1)
92+
cls.init_robot(cls)
93+
94+
@classmethod
95+
def tearDownClass(cls):
96+
# Shutdown the ROS context
97+
cls.node.destroy_node()
98+
rclpy.shutdown()
99+
100+
def init_robot(self):
101+
self._dashboard_interface = DashboardInterface(self.node)
102+
self._controller_manager_interface = ControllerManagerInterface(self.node)
103+
self._io_status_controller_interface = IoStatusInterface(self.node)
104+
self._configuration_controller_interface = ConfigurationInterface(self.node)
105+
106+
def setUp(self):
107+
self._dashboard_interface.start_robot()
108+
time.sleep(1)
109+
self.assertTrue(
110+
self._io_status_controller_interface.resend_robot_program().success
111+
)
112+
self.tf_buffer = Buffer()
113+
self.tf_listener = TransformListener(self.tf_buffer, self.node)
114+
115+
def test_force_mode_controller(self, tf_prefix):
116+
self.assertTrue(
117+
self._controller_manager_interface.switch_controller(
118+
strictness=SwitchController.Request.BEST_EFFORT,
119+
activate_controllers=[
120+
"force_mode_controller",
121+
],
122+
deactivate_controllers=[
123+
"scaled_joint_trajectory_controller",
124+
"joint_trajectory_controller",
125+
],
126+
).ok
127+
)
128+
self._force_mode_controller_interface = ForceModeInterface(self.node)
129+
130+
# Create task frame for force mode
131+
point = Point(x=0.8, y=0.8, z=0.8)
132+
orientation = Quaternion(x=0.7071, y=0.0, z=0.0, w=0.7071)
133+
task_frame_pose = Pose()
134+
task_frame_pose.position = point
135+
task_frame_pose.orientation = orientation
136+
header = std_msgs.msg.Header(seq=1, frame_id=tf_prefix + "base")
137+
header.stamp.sec = int(time.time()) + 1
138+
header.stamp.nanosec = 0
139+
frame_stamp = PoseStamped()
140+
frame_stamp.header = header
141+
frame_stamp.pose = task_frame_pose
142+
143+
# Create compliance vector (which axes should be force controlled)
144+
compliance = [False, False, True, False, False, False]
145+
146+
# Create Wrench message for force mode
147+
wrench = Wrench()
148+
wrench.force = Vector3(x=0.0, y=0.0, z=5.0)
149+
wrench.torque = Vector3(x=0.0, y=0.0, z=0.0)
150+
151+
# Specify interpretation of task frame (no transform)
152+
type_spec = 2
153+
154+
# Specify max speeds and deviations of force mode
155+
speed_limits = Twist()
156+
speed_limits.linear = Vector3(x=0.0, y=0.0, z=1.0)
157+
speed_limits.angular = Vector3(x=0.0, y=0.0, z=1.0)
158+
deviation_limits = [1.0, 1.0, 1.0, 0.5, 1.0, 1.0, 1.0]
159+
160+
# specify damping and gain scaling
161+
damping_factor = 0.1
162+
gain_scale = 0.8
163+
164+
trans_before = None
165+
while not trans_before:
166+
rclpy.spin_once(self.node)
167+
try:
168+
trans_before = self.tf_buffer.lookup_transform(
169+
tf_prefix + "base", tf_prefix + "tool0", rclpy.time.Time()
170+
)
171+
except TransformException as ex:
172+
pass
173+
174+
# Send request to controller
175+
res = self._force_mode_controller_interface.start_force_mode(
176+
task_frame=frame_stamp,
177+
selection_vector_x=compliance[0],
178+
selection_vector_y=compliance[1],
179+
selection_vector_z=compliance[2],
180+
selection_vector_rx=compliance[3],
181+
selection_vector_ry=compliance[4],
182+
selection_vector_rz=compliance[5],
183+
wrench=wrench,
184+
type=type_spec,
185+
speed_limits=speed_limits,
186+
deviation_limits=deviation_limits,
187+
damping_factor=damping_factor,
188+
gain_scaling=gain_scale,
189+
)
190+
self.assertTrue(res.success)
191+
192+
time.sleep(5.0)
193+
194+
trans_after = None
195+
timepoint = self.node.get_clock().now()
196+
while not trans_after:
197+
rclpy.spin_once(self.node)
198+
try:
199+
trans_after = self.tf_buffer.lookup_transform(
200+
tf_prefix + "base", tf_prefix + "tool0", timepoint
201+
)
202+
except TransformException as ex:
203+
pass
204+
205+
# task frame and wrench determines the expected motion
206+
# In the example we used
207+
# - a task frame rotated pi/2 deg around the base frame's x axis
208+
# - a wrench with a positive z component for the force
209+
# => we should expect a motion in negative y of the base frame
210+
self.assertTrue(
211+
trans_after.transform.translation.y < trans_before.transform.translation.y
212+
)
213+
self.assertAlmostEqual(
214+
trans_after.transform.translation.x,
215+
trans_before.transform.translation.x,
216+
delta=0.001,
217+
)
218+
self.assertAlmostEqual(
219+
trans_after.transform.translation.z,
220+
trans_before.transform.translation.z,
221+
delta=0.001,
222+
)
223+
self.assertAlmostEqual(
224+
trans_after.transform.rotation.x,
225+
trans_before.transform.rotation.x,
226+
delta=0.01,
227+
)
228+
self.assertAlmostEqual(
229+
trans_after.transform.rotation.y,
230+
trans_before.transform.rotation.y,
231+
delta=0.01,
232+
)
233+
self.assertAlmostEqual(
234+
trans_after.transform.rotation.z,
235+
trans_before.transform.rotation.z,
236+
delta=0.01,
237+
)
238+
self.assertAlmostEqual(
239+
trans_after.transform.rotation.w,
240+
trans_before.transform.rotation.w,
241+
delta=0.01,
242+
)
243+
244+
res = self._force_mode_controller_interface.stop_force_mode()
245+
self.assertTrue(res.success)
246+
247+
# Deactivate controller
248+
self.assertTrue(
249+
self._controller_manager_interface.switch_controller(
250+
strictness=SwitchController.Request.STRICT,
251+
deactivate_controllers=["force_mode_controller"],
252+
).ok
253+
)

0 commit comments

Comments
 (0)