diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 3a5b3a0a9..e45fabbdb 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,13 @@ Changelog for package ur_client_library ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +1.7.1 (2025-02-25) +------------------ +* Fix trajectory result in trajectory forward mode when no trajectory is running (`#276 `_) +* Remove sending an idle command in quintic spline test (`#275 `_) +* In servo mode always allow targets close to current pose (`#273 `_) +* Contributors: Felix Exner + 1.7.0 (2025-02-19) ------------------ * Make UrDriver tests run without ctest (`#270 `_) diff --git a/package.xml b/package.xml index 193ddb73a..87ffa2017 100644 --- a/package.xml +++ b/package.xml @@ -2,7 +2,7 @@ ur_client_library - 1.7.0 + 1.7.1 Standalone C++ library for accessing Universal Robots interfaces. This has been forked off the ur_robot_driver. Thomas Timm Andersen Simon Rasmussen diff --git a/resources/external_control.urscript b/resources/external_control.urscript index 01a212097..4096a0598 100644 --- a/resources/external_control.urscript +++ b/resources/external_control.urscript @@ -153,6 +153,14 @@ def set_servo_setpoint(q): if targetWithinLimits(cmd_servo_q, q, steptime): cmd_servo_q_last = cmd_servo_q cmd_servo_q = q + # If the target has been interpolating too far away from the robot, so it would trigger the limit + # check, but the target is close to where the robot currently is, also accept the command. + # This can, for example, happen if a command series was too fast for the robot to follow, which + # triggers a path deviation on the commanding side and that is mitigated by a hold-position + # command. + elif targetWithinLimits(get_joint_positions(), q, steptime): + cmd_servo_q_last = cmd_servo_q + cmd_servo_q = q end end @@ -690,9 +698,11 @@ while control_mode > MODE_STOPPED: # Clear remaining trajectory points if control_mode == MODE_FORWARD: kill thread_trajectory - clear_remaining_trajectory_points() - stopj(STOPJ_ACCELERATION) - socket_send_int(TRAJECTORY_RESULT_CANCELED, "trajectory_socket") + if trajectory_points_left > 0: + clear_remaining_trajectory_points() + stopj(STOPJ_ACCELERATION) + socket_send_int(TRAJECTORY_RESULT_CANCELED, "trajectory_socket") + end # Stop freedrive elif control_mode == MODE_FREEDRIVE: textmsg("Leaving freedrive mode") diff --git a/tests/test_spline_interpolation.cpp b/tests/test_spline_interpolation.cpp index 1d7893553..6a389491e 100644 --- a/tests/test_spline_interpolation.cpp +++ b/tests/test_spline_interpolation.cpp @@ -36,6 +36,7 @@ #include #include +#include #include #include #include @@ -56,10 +57,15 @@ bool g_HEADLESS = true; std::unique_ptr g_my_robot; bool g_trajectory_running; +std::condition_variable g_trajectory_result_cv; +std::mutex g_trajectory_result_mutex; control::TrajectoryResult g_trajectory_result; void handleTrajectoryState(control::TrajectoryResult state) { + std::lock_guard lk(g_trajectory_result_mutex); + g_trajectory_result_cv.notify_one(); g_trajectory_result = state; + URCL_LOG_INFO("Received trajectory result %s", control::trajectoryResultToString(state).c_str()); g_trajectory_running = false; } @@ -74,6 +80,12 @@ bool nearlyEqual(double a, double b, double eps = 1e-15) return c < eps || -c < eps; } +bool waitForTrajectoryResult(std::chrono::milliseconds timeout) +{ + std::unique_lock lk(g_trajectory_result_mutex); + return g_trajectory_result_cv.wait_for(lk, timeout) == std::cv_status::no_timeout; +} + class SplineInterpolationTest : public ::testing::Test { protected: @@ -141,11 +153,6 @@ class SplineInterpolationTest : public ::testing::Test ASSERT_TRUE(g_my_robot->isHealthy()); } - void sendIdle() - { - ASSERT_TRUE(g_my_robot->ur_driver_->writeKeepalive(RobotReceiveTimeout::sec())); - } - void sendTrajectory(const std::vector& s_pos, const std::vector& s_vel, const std::vector& s_acc, const std::vector& s_time) { @@ -458,10 +465,6 @@ TEST_F(SplineInterpolationTest, quintic_spline_with_end_point_velocity_with_spee std::unique_ptr data_pkg; g_my_robot->readDataPackage(data_pkg); - // Align timestep - sendIdle(); - g_my_robot->readDataPackage(data_pkg); - urcl::vector6d_t joint_positions, joint_velocities, joint_acc; ASSERT_TRUE(data_pkg->getData("target_q", joint_positions)); ASSERT_TRUE(data_pkg->getData("target_qd", joint_velocities)); @@ -1094,6 +1097,52 @@ TEST_F(SplineInterpolationTest, physically_unfeasible_trajectory_quintic_spline) } } +TEST_F(SplineInterpolationTest, switching_control_mode_without_trajectory_produces_no_result) +{ + // We start by putting the robot into trajectory control mode + ASSERT_TRUE( + g_my_robot->ur_driver_->writeTrajectoryControlMessage(urcl::control::TrajectoryControlMessage::TRAJECTORY_NOOP)); + + // Then we switch to idle + ASSERT_TRUE(g_my_robot->ur_driver_->writeKeepalive(RobotReceiveTimeout::sec(2))); + + EXPECT_FALSE(waitForTrajectoryResult(std::chrono::milliseconds(500))); +} + +TEST_F(SplineInterpolationTest, switching_control_mode_with_trajectory_produces_result) +{ + g_my_robot->stopConsumingRTDEData(); + std::unique_ptr data_pkg; + g_my_robot->readDataPackage(data_pkg); + + urcl::vector6d_t joint_positions_before; + ASSERT_TRUE(data_pkg->getData("target_q", joint_positions_before)); + + // Start consuming rtde packages to avoid pipeline overflows while testing that the control script aborts correctly + g_my_robot->startConsumingRTDEData(); + // Create a trajectory that cannot be executed within the robots limits + std::vector s_pos, s_vel, s_acc; + urcl::vector6d_t first_point = { + joint_positions_before[0], joint_positions_before[1], joint_positions_before[2], + joint_positions_before[3], joint_positions_before[4], joint_positions_before[5] + 0.5 + }; + s_pos.push_back(first_point); + + urcl::vector6d_t zeros = { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }; + s_vel.push_back(zeros); + s_acc.push_back(zeros); + + std::vector s_time = { 0.02 }; + sendTrajectory(s_pos, s_vel, s_acc, s_time); + g_my_robot->ur_driver_->writeTrajectoryControlMessage(urcl::control::TrajectoryControlMessage::TRAJECTORY_NOOP, -1, + RobotReceiveTimeout::off()); + + // Then we switch to idle + ASSERT_TRUE(g_my_robot->ur_driver_->writeKeepalive(RobotReceiveTimeout::sec(2))); + + EXPECT_TRUE(waitForTrajectoryResult(std::chrono::milliseconds(500))); +} + int main(int argc, char* argv[]) { ::testing::InitGoogleTest(&argc, argv);