Skip to content

Commit 6ec1ee0

Browse files
authored
Improve cleanup of trajectory buffer (UniversalRobots#466)
**Summary** When cancelling a trajectory (e.g. a spline trajectory) while it is still being executed, the `external_control.urscript` currently attempts to clear the entire trajectory buffer within a single control cycle. If the trajectory buffer contains a large number of points (≈200 or more), this cleanup can exceed the real‑time execution budget of the UR controller, causing a Protective Stop (PSTOP) with error: _C271A1:6 – Low Level real-time thread: Runtime is too much behind_ In practice, this means that clearing a large trajectory buffer can block the real‑time thread long enough to trigger a safety stop on the robot. To avoid blocking the real‑time thread, trajectory cleanup is now performed incrementally across multiple cycles instead of in a single cycle.
1 parent bae79c9 commit 6ec1ee0

2 files changed

Lines changed: 167 additions & 9 deletions

File tree

resources/external_control.urscript

Lines changed: 59 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,10 @@ global coulomb_scale = [0.8, 0.8, 0.7, 0.8, 0.8, 0.8]
106106
thread_move = 0
107107
thread_trajectory = 0
108108
thread_script_commands = 0
109+
thread_clear = 0
110+
111+
# Global thread flags
112+
global trajectory_cleanup_running = False
109113

110114
###
111115
# @brief Function to verify whether the specified target can be reached within the defined time frame while staying within
@@ -614,7 +618,9 @@ thread trajectoryThread():
614618
is_robot_moving = quinticSplineRun(q, qd, qdd, tmptime, is_last_point, is_first_point)
615619
else:
616620
textmsg("Unknown spline type given:", raw_point[INDEX_POINT_TYPE])
617-
clear_remaining_trajectory_points()
621+
request_trajectory_cleanup()
622+
stopj(STOPJ_ACCELERATION)
623+
wait_for_trajectory_cleanup()
618624
trajectory_result = TRAJECTORY_RESULT_FAILURE
619625
end
620626

@@ -687,13 +693,50 @@ thread trajectoryThread():
687693
textmsg("Trajectory finished with result ", trajectory_result_to_str(trajectory_result))
688694
end
689695

690-
def clear_remaining_trajectory_points():
696+
# Thread to cleanup the remaining trajectory_points_left when trajectory canceled
697+
thread clearTrajectoryPointsThread():
698+
local reads = 0
699+
local limit = 0
700+
local timeout = 0.5
701+
702+
{% if ROBOT_SOFTWARE_VERSION >= v10.0.0 %}
703+
limit = 20
704+
{% else %}
705+
limit = 10
706+
{% endif %}
707+
691708
while trajectory_points_left > 0:
692-
raw_point = socket_read_binary_integer(TRAJECTORY_DATA_DIMENSION + 2, "trajectory_socket")
709+
raw_point = socket_read_binary_integer(TRAJECTORY_DATA_DIMENSION + 2, "trajectory_socket", timeout)
710+
if raw_point[0] <= 0:
711+
trajectory_points_left = 0
712+
textmsg("No more trajectory points in the buffer. Resetting trajectory_points_left to 0.")
713+
break
714+
end
693715
trajectory_points_left = trajectory_points_left - 1
716+
reads = reads + 1
717+
if reads >= limit:
718+
sync()
719+
reads = 0
720+
end
721+
end
722+
textmsg("Trajectory point buffer cleared successfully.")
723+
trajectory_cleanup_running = False
724+
end
725+
726+
# Checks that the thread is only called once
727+
def request_trajectory_cleanup():
728+
if not trajectory_cleanup_running:
729+
trajectory_cleanup_running = True
730+
thread_clear = run clearTrajectoryPointsThread()
694731
end
695732
end
696733

734+
# Wait until the trajectory cleanup has finished
735+
def wait_for_trajectory_cleanup():
736+
join thread_clear
737+
thread_clear = 0
738+
end
739+
697740
# Helpers for speed control
698741
def set_speedl(twist):
699742
cmd_twist = twist
@@ -755,7 +798,7 @@ def tool_contact_detection():
755798
if step_back > 0:
756799
if control_mode == MODE_FORWARD:
757800
kill thread_trajectory
758-
clear_remaining_trajectory_points()
801+
request_trajectory_cleanup()
759802
elif control_mode == MODE_FREEDRIVE:
760803
textmsg("Leaving freedrive mode")
761804
end_freedrive_mode()
@@ -766,7 +809,7 @@ def tool_contact_detection():
766809
# Set control mode to tool in contact, should be cleared by stopping tool contact detection
767810
control_mode = MODE_TOOL_IN_CONTACT
768811
stopl(3)
769-
812+
wait_for_trajectory_cleanup()
770813
# Move to initial contact point
771814
q = get_actual_joint_positions_history(step_back)
772815
movel(q)
@@ -935,6 +978,7 @@ end
935978
control_mode = MODE_UNINITIALIZED
936979
thread_move = 0
937980
thread_trajectory = 0
981+
thread_clear = 0
938982
trajectory_points_left = 0
939983
textmsg("ExternalControl: External control active")
940984
global read_timeout = 0.0 # First read is blocking
@@ -952,8 +996,9 @@ while control_mode > MODE_STOPPED:
952996
if control_mode == MODE_FORWARD:
953997
kill thread_trajectory
954998
if trajectory_points_left > 0:
955-
clear_remaining_trajectory_points()
999+
request_trajectory_cleanup()
9561000
stopj(STOPJ_ACCELERATION)
1001+
wait_for_trajectory_cleanup()
9571002
socket_send_int(TRAJECTORY_RESULT_CANCELED, "trajectory_socket")
9581003
end
9591004
# Stop freedrive
@@ -1000,16 +1045,21 @@ while control_mode > MODE_STOPPED:
10001045
torque = [params_mult[2]/ MULT_jointstate, params_mult[3]/ MULT_jointstate, params_mult[4]/ MULT_jointstate, params_mult[5]/ MULT_jointstate, params_mult[6]/ MULT_jointstate, params_mult[7]/ MULT_jointstate]
10011046
set_torque(torque)
10021047
elif control_mode == MODE_FORWARD:
1003-
if params_mult[2] == TRAJECTORY_MODE_RECEIVE:
1048+
if trajectory_cleanup_running:
1049+
textmsg("Trajectory buffer cleanup in progress, new trajectory ignored")
1050+
popup("Trajectory buffer cleanup in progress, new trajectory ignored", error = True, blocking = True)
1051+
elif params_mult[2] == TRAJECTORY_MODE_RECEIVE:
10041052
kill thread_trajectory
1005-
clear_remaining_trajectory_points()
1053+
request_trajectory_cleanup()
1054+
wait_for_trajectory_cleanup()
10061055
trajectory_points_left = params_mult[3]
10071056
thread_trajectory = run trajectoryThread()
10081057
elif params_mult[2] == TRAJECTORY_MODE_CANCEL:
10091058
textmsg("cancel received")
10101059
kill thread_trajectory
1011-
clear_remaining_trajectory_points()
1060+
request_trajectory_cleanup()
10121061
stopj(STOPJ_ACCELERATION)
1062+
wait_for_trajectory_cleanup()
10131063
socket_send_int(TRAJECTORY_RESULT_CANCELED, "trajectory_socket")
10141064
end
10151065
elif control_mode == MODE_SPEEDL:

tests/test_spline_interpolation.cpp

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1115,6 +1115,114 @@ TEST_F(SplineInterpolationTest, switching_control_mode_with_trajectory_produces_
11151115
EXPECT_TRUE(waitForTrajectoryResult(std::chrono::milliseconds(500)));
11161116
}
11171117

1118+
TEST_F(SplineInterpolationTest, new_trajectory_received_without_cancelling_the_one_executed)
1119+
{
1120+
rtde_interface::DataPackage data_pkg(g_my_robot->getUrDriver()->getRTDEOutputRecipe());
1121+
ASSERT_TRUE(g_my_robot->getUrDriver()->getDataPackage(data_pkg));
1122+
1123+
urcl::vector6d_t joint_positions_before{ 0, 0, 0, 0, 0, 0 };
1124+
ASSERT_TRUE(data_pkg.getData("target_q", joint_positions_before));
1125+
1126+
std::vector<urcl::vector6d_t> s_pos, s_vel, s_acc;
1127+
std::vector<urcl::vector6d_t> positions{ { -1.57, -1.57, 0, 0, 0, 0 },
1128+
{ -1.57, -1.57, -1.57, 0, 0, 0 },
1129+
{ -1.57, -1.57, 0, 0, 0, 0 },
1130+
{ -1.57, -1.6, 1.6, -0.7, 0.7, 0.2 } };
1131+
std::vector<urcl::vector6d_t> velocities{
1132+
{ 0, 0, 0.0, 0, 0, 0 }, { 0, 0, 0.0, 0, 0, 0 }, { 0, 0, 1.5, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0 }
1133+
};
1134+
std::vector<double> s_time{ 3.0, 3.0, 3.0, 3.0 };
1135+
double increment = 0.005;
1136+
double duration = 0.1;
1137+
1138+
// Generate a trajectory with different target points
1139+
for (size_t i = 0; i < 5; ++i)
1140+
{
1141+
s_pos.push_back({ -1.57, -1.6, 1.6, -0.7, 0.7 + i * increment, 0.2 });
1142+
s_vel.push_back({ 0, 0, 0, 0, increment / duration, 0 });
1143+
s_acc.push_back({ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 });
1144+
s_time.push_back(duration);
1145+
}
1146+
1147+
// Send the trajectory
1148+
sendTrajectory(s_pos, s_vel, s_acc, s_time);
1149+
waitForTrajectoryStarted();
1150+
1151+
// Send the trajectory again without canceling the before one
1152+
sendTrajectory(s_pos, s_vel, s_acc, s_time);
1153+
g_trajectory_running = true;
1154+
waitForTrajectoryStarted();
1155+
1156+
// Ensure that everything goes as expected and the robot reaches the target position
1157+
urcl::vector6d_t joint_positions;
1158+
while (g_trajectory_running)
1159+
{
1160+
ASSERT_TRUE(g_my_robot->getUrDriver()->getDataPackage(data_pkg));
1161+
ASSERT_TRUE(data_pkg.getData("target_q", joint_positions));
1162+
// Keep connection alive
1163+
ASSERT_TRUE(g_my_robot->getUrDriver()->writeTrajectoryControlMessage(
1164+
urcl::control::TrajectoryControlMessage::TRAJECTORY_NOOP));
1165+
}
1166+
EXPECT_EQ(control::TrajectoryResult::TRAJECTORY_RESULT_SUCCESS, g_trajectory_result);
1167+
urcl::vector6d_t joint_positions_after;
1168+
ASSERT_TRUE(data_pkg.getData("actual_q", joint_positions_after));
1169+
const auto& expected_final = s_pos.back();
1170+
for (unsigned int i = 0; i < 6; ++i)
1171+
{
1172+
EXPECT_NEAR(joint_positions[i], expected_final[i], eps_);
1173+
}
1174+
}
1175+
1176+
TEST_F(SplineInterpolationTest, cancel_trajectory_while_being_executed_and_sending_new_one)
1177+
{
1178+
rtde_interface::DataPackage data_pkg(g_my_robot->getUrDriver()->getRTDEOutputRecipe());
1179+
ASSERT_TRUE(g_my_robot->getUrDriver()->getDataPackage(data_pkg));
1180+
1181+
urcl::vector6d_t joint_positions_before{ 0, 0, 0, 0, 0, 0 };
1182+
ASSERT_TRUE(data_pkg.getData("target_q", joint_positions_before));
1183+
1184+
std::vector<urcl::vector6d_t> s_pos, s_vel, s_acc;
1185+
std::vector<urcl::vector6d_t> positions{ { -1.57, -1.57, 0, 0, 0, 0 },
1186+
{ -1.57, -1.57, -1.57, 0, 0, 0 },
1187+
{ -1.57, -1.57, 0, 0, 0, 0 },
1188+
{ -1.57, -1.6, 1.6, -0.7, 0.7, 0.2 } };
1189+
std::vector<urcl::vector6d_t> velocities{
1190+
{ 0, 0, 0.0, 0, 0, 0 }, { 0, 0, 0.0, 0, 0, 0 }, { 0, 0, 1.5, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0 }
1191+
};
1192+
std::vector<double> s_time{ 3.0, 3.0, 3.0, 3.0 };
1193+
double increment = 0.005;
1194+
double duration = 0.1;
1195+
1196+
// Generate a trajectory with 500 points
1197+
for (size_t i = 0; i < 500; ++i)
1198+
{
1199+
s_pos.push_back({ -1.57, -1.6, 1.6, -0.7, 0.7 + i * increment, 0.2 });
1200+
s_vel.push_back({ 0, 0, 0, 0, increment / duration, 0 });
1201+
s_acc.push_back({ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 });
1202+
s_time.push_back(duration);
1203+
}
1204+
1205+
// Send the trajectory and cancel it right away
1206+
sendTrajectory(s_pos, s_vel, s_acc, s_time);
1207+
waitForTrajectoryStarted();
1208+
1209+
ASSERT_TRUE(g_my_robot->getUrDriver()->writeTrajectoryControlMessage(
1210+
urcl::control::TrajectoryControlMessage::TRAJECTORY_CANCEL));
1211+
EXPECT_TRUE(waitForTrajectoryResult(std::chrono::milliseconds(500)));
1212+
// Ensure we receive the cancel trajectory result, which will mean that the trajectory buffer will be clear
1213+
EXPECT_EQ(control::TrajectoryResult::TRAJECTORY_RESULT_CANCELED, g_trajectory_result);
1214+
1215+
// Do the same procesure again to ensure that a new trajectory can be send after canceling the before
1216+
// one in the middle of its execution
1217+
sendTrajectory(s_pos, s_vel, s_acc, s_time);
1218+
waitForTrajectoryStarted();
1219+
1220+
ASSERT_TRUE(g_my_robot->getUrDriver()->writeTrajectoryControlMessage(
1221+
urcl::control::TrajectoryControlMessage::TRAJECTORY_CANCEL));
1222+
EXPECT_TRUE(waitForTrajectoryResult(std::chrono::milliseconds(500)));
1223+
EXPECT_EQ(control::TrajectoryResult::TRAJECTORY_RESULT_CANCELED, g_trajectory_result);
1224+
}
1225+
11181226
int main(int argc, char* argv[])
11191227
{
11201228
::testing::InitGoogleTest(&argc, argv);

0 commit comments

Comments
 (0)