Skip to content

Commit 19257be

Browse files
authored
Fix/passthrough controller very short trajectories (backport #1940) (#1957)
* Add integration test for 0 time trajectories * Correctly handle very short 1pt trajectories for passthrough
1 parent f93f50d commit 19257be

2 files changed

Lines changed: 76 additions & 41 deletions

File tree

ur_robot_driver/src/hardware_interface.cpp

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1688,18 +1688,18 @@ void URPositionHardwareInterface::check_passthrough_trajectory_controller()
16881688
ur_driver_->writeTrajectoryControlMessage(urcl::control::TrajectoryControlMessage::TRAJECTORY_CANCEL);
16891689
} else if (passthrough_trajectory_transfer_state_ == 6.0) {
16901690
if (passthrough_trajectory_size_ != trajectory_joint_positions_.size()) {
1691-
RCLCPP_INFO(get_logger(), "Got a new trajectory with %lu points.",
1692-
static_cast<size_t>(passthrough_trajectory_size_));
16931691
trajectory_joint_positions_.resize(passthrough_trajectory_size_);
16941692
trajectory_joint_velocities_.resize(passthrough_trajectory_size_);
16951693
trajectory_joint_accelerations_.resize(passthrough_trajectory_size_);
16961694
trajectory_times_.resize(passthrough_trajectory_size_);
1697-
point_index_received = 0;
1698-
point_index_sent = 0;
1699-
trajectory_started = false;
1700-
last_time = 0.0;
1701-
passthrough_trajectory_transfer_state_ = 1.0;
17021695
}
1696+
RCLCPP_INFO(get_logger(), "Got a new trajectory with %lu points.",
1697+
static_cast<size_t>(passthrough_trajectory_size_));
1698+
point_index_received = 0;
1699+
point_index_sent = 0;
1700+
trajectory_started = false;
1701+
last_time = 0.0;
1702+
passthrough_trajectory_transfer_state_ = 1.0;
17031703
} else if (passthrough_trajectory_transfer_state_ == 2.0) {
17041704
passthrough_trajectory_abort_ = 0.0;
17051705
trajectory_joint_positions_[point_index_received] = passthrough_trajectory_positions_;
@@ -1715,7 +1715,7 @@ void URPositionHardwareInterface::check_passthrough_trajectory_controller()
17151715

17161716
// Once we received enough points so we can move for at least 5 cycles, we start executing
17171717
if ((passthrough_trajectory_time_from_start_ > 5.0 / static_cast<double>(info_.rw_rate) ||
1718-
point_index_received == passthrough_trajectory_size_ - 1) &&
1718+
point_index_received == passthrough_trajectory_size_) &&
17191719
!trajectory_started) {
17201720
ur_driver_->writeTrajectoryControlMessage(urcl::control::TrajectoryControlMessage::TRAJECTORY_START,
17211721
trajectory_joint_positions_.size());

ur_robot_driver/test/integration_test_passthrough_controller.py

Lines changed: 68 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
from control_msgs.msg import JointTolerance
4242
from controller_manager_msgs.srv import SwitchController
4343
from rclpy.node import Node
44+
from sensor_msgs.msg import JointState
4445
from trajectory_msgs.msg import JointTrajectory, JointTrajectoryPoint
4546

4647
sys.path.append(os.path.dirname(__file__))
@@ -116,11 +117,7 @@ def setUp(self):
116117
time.sleep(1)
117118
self.assertTrue(self._io_status_controller_interface.resend_robot_program().success)
118119

119-
#
120-
# Test functions
121-
#
122-
123-
def test_start_passthrough_controller(self):
120+
def _activate_passthrough_controller(self):
124121
self.assertTrue(
125122
self._controller_manager_interface.switch_controller(
126123
strictness=SwitchController.Request.BEST_EFFORT,
@@ -129,14 +126,59 @@ def test_start_passthrough_controller(self):
129126
).ok
130127
)
131128

132-
def test_passthrough_trajectory(self, tf_prefix):
133-
self.assertTrue(
134-
self._controller_manager_interface.switch_controller(
135-
strictness=SwitchController.Request.BEST_EFFORT,
136-
activate_controllers=["passthrough_trajectory_controller"],
137-
deactivate_controllers=["joint_trajectory_controller"],
138-
).ok
129+
def _get_current_joint_positions(self, tf_prefix, timeout=5.0):
130+
last_joint_state = None
131+
132+
def js_cb(msg):
133+
nonlocal last_joint_state
134+
last_joint_state = msg
135+
136+
joint_state_sub = self.node.create_subscription(JointState, "/joint_states", js_cb, 1)
137+
end_time = time.time() + timeout
138+
while last_joint_state is None and time.time() < end_time:
139+
rclpy.spin_once(self.node, timeout_sec=0.1)
140+
self.node.destroy_subscription(joint_state_sub)
141+
self.assertIsNotNone(last_joint_state)
142+
143+
joint_names = [tf_prefix + joint for joint in ROBOT_JOINTS]
144+
return [
145+
last_joint_state.position[last_joint_state.name.index(name)] for name in joint_names
146+
]
147+
148+
def _send_single_point_trajectory_at_current_pose(self, tf_prefix):
149+
joint_names = [tf_prefix + joint for joint in ROBOT_JOINTS]
150+
positions = self._get_current_joint_positions(tf_prefix)
151+
trajectory = JointTrajectory(
152+
joint_names=joint_names,
153+
points=[
154+
JointTrajectoryPoint(
155+
positions=positions,
156+
time_from_start=Duration(sec=0, nanosec=0),
157+
)
158+
],
139159
)
160+
goal_tolerance = [JointTolerance(position=0.01, name=name) for name in joint_names]
161+
goal_time_tolerance = Duration(sec=1, nanosec=0)
162+
goal_handle = self._passthrough_forward_joint_trajectory.send_goal(
163+
trajectory=trajectory,
164+
goal_time_tolerance=goal_time_tolerance,
165+
goal_tolerance=goal_tolerance,
166+
)
167+
self.assertTrue(goal_handle.accepted)
168+
result = self._passthrough_forward_joint_trajectory.get_result(
169+
goal_handle, TIMEOUT_EXECUTE_TRAJECTORY
170+
)
171+
self.assertEqual(result.error_code, FollowJointTrajectory.Result.SUCCESSFUL)
172+
173+
#
174+
# Test functions
175+
#
176+
177+
def test_start_passthrough_controller(self):
178+
self._activate_passthrough_controller()
179+
180+
def test_passthrough_trajectory(self, tf_prefix):
181+
self._activate_passthrough_controller()
140182

141183
goal_tolerance = [
142184
JointTolerance(position=0.01, name=tf_prefix + joint) for joint in ROBOT_JOINTS
@@ -161,15 +203,20 @@ def test_passthrough_trajectory(self, tf_prefix):
161203
)
162204
self.assertEqual(result.error_code, FollowJointTrajectory.Result.SUCCESSFUL)
163205

206+
def test_single_point_trajectory_at_current_pose(self, tf_prefix):
207+
"""A trajectory with only one point at time 0.0 at the current pose should succeed."""
208+
self._activate_passthrough_controller()
209+
self._send_single_point_trajectory_at_current_pose(tf_prefix)
210+
211+
def test_consecutive_single_point_trajectories(self, tf_prefix):
212+
"""Two consecutive single-point trajectories at time 0.0 should both succeed."""
213+
self._activate_passthrough_controller()
214+
self._send_single_point_trajectory_at_current_pose(tf_prefix)
215+
self._send_single_point_trajectory_at_current_pose(tf_prefix)
216+
164217
def test_quintic_trajectory(self, tf_prefix):
165218
# Full quintic trajectory
166-
self.assertTrue(
167-
self._controller_manager_interface.switch_controller(
168-
strictness=SwitchController.Request.BEST_EFFORT,
169-
activate_controllers=["passthrough_trajectory_controller"],
170-
deactivate_controllers=["joint_trajectory_controller"],
171-
).ok
172-
)
219+
self._activate_passthrough_controller()
173220
trajectory = JointTrajectory(
174221
points=[
175222
JointTrajectoryPoint(
@@ -201,13 +248,7 @@ def test_quintic_trajectory(self, tf_prefix):
201248

202249
def test_impossible_goal_tolerance_fails(self, tf_prefix):
203250
# Test impossible goal tolerance, should fail.
204-
self.assertTrue(
205-
self._controller_manager_interface.switch_controller(
206-
strictness=SwitchController.Request.BEST_EFFORT,
207-
activate_controllers=["passthrough_trajectory_controller"],
208-
deactivate_controllers=["joint_trajectory_controller"],
209-
).ok
210-
)
251+
self._activate_passthrough_controller()
211252
trajectory = JointTrajectory(
212253
points=[
213254
JointTrajectoryPoint(positions=pos, time_from_start=times)
@@ -235,13 +276,7 @@ def test_impossible_goal_tolerance_fails(self, tf_prefix):
235276

236277
def test_impossible_goal_time_tolerance_fails(self, tf_prefix):
237278
# Test impossible goal time
238-
self.assertTrue(
239-
self._controller_manager_interface.switch_controller(
240-
strictness=SwitchController.Request.BEST_EFFORT,
241-
activate_controllers=["passthrough_trajectory_controller"],
242-
deactivate_controllers=["joint_trajectory_controller"],
243-
).ok
244-
)
279+
self._activate_passthrough_controller()
245280

246281
goal_tolerance = [
247282
JointTolerance(position=0.01, name=tf_prefix + joint) for joint in ROBOT_JOINTS

0 commit comments

Comments
 (0)