Skip to content

Commit 245c01a

Browse files
authored
instruction_executor: Allow canceling an instruction (#281)
This allows cancelling a running motion with the InstructionExecutor. As executions are blocking, they have to be run in another thread to be able to be canceled.
1 parent dafaedf commit 245c01a

File tree

7 files changed

+163
-4
lines changed

7 files changed

+163
-4
lines changed

include/ur_client_library/control/reverse_interface.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,16 @@ class ReverseInterface
146146
disconnection_callback_ = disconnection_fun;
147147
}
148148

149+
/*!
150+
* \brief Checks if the reverse interface is connected to the robot.
151+
*
152+
* \returns True, if the interface is connected, false otherwise.
153+
*/
154+
bool isConnected() const
155+
{
156+
return client_fd_ != INVALID_SOCKET;
157+
}
158+
149159
protected:
150160
virtual void connectionCallback(const socket_t filedescriptor);
151161

include/ur_client_library/ur/instruction_executor.h

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,12 @@ class InstructionExecutor
4848
std::bind(&InstructionExecutor::trajDisconnectCallback, this, std::placeholders::_1));
4949
}
5050

51+
~InstructionExecutor()
52+
{
53+
driver_->registerTrajectoryDoneCallback(nullptr);
54+
driver_->registerTrajectoryInterfaceDisconnectedCallback(nullptr);
55+
}
56+
5157
/**
5258
* \brief Execute a sequence of motion primitives.
5359
*
@@ -92,14 +98,35 @@ class InstructionExecutor
9298
bool moveL(const urcl::Pose& target, const double acceleration = 1.4, const double velocity = 1.04,
9399
const double time = 0, const double blend_radius = 0);
94100

101+
/**
102+
* \brief Cancel the current motion.
103+
*
104+
* If no motion is running, false will be returned.
105+
* If a motion is running, it will be canceled and it will wait for a TRAJECTORY_CANCELED result
106+
* with a timeout of one second.
107+
*
108+
* If another result or no result is received, false will be returned.
109+
*/
110+
bool cancelMotion();
111+
112+
/**
113+
* \brief Check if a trajectory is currently running.
114+
*/
115+
bool isTrajectoryRunning() const
116+
{
117+
return trajectory_running_;
118+
}
119+
95120
private:
96121
void trajDoneCallback(const urcl::control::TrajectoryResult& result);
97122
void trajDisconnectCallback(const int filedescriptor);
98123
std::shared_ptr<urcl::UrDriver> driver_;
99124
std::atomic<bool> trajectory_running_ = false;
125+
std::atomic<bool> cancel_requested_ = false;
100126
std::mutex trajectory_result_mutex_;
127+
std::condition_variable trajectory_done_cv_;
101128
urcl::control::TrajectoryResult trajectory_result_;
102129
};
103130
} // namespace urcl
104131

105-
#endif // UR_CLIENT_LIBRARY_INSTRUCTION_EXECUTOR_H_INCLUDED
132+
#endif // UR_CLIENT_LIBRARY_INSTRUCTION_EXECUTOR_H_INCLUDED

include/ur_client_library/ur/ur_driver.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -880,6 +880,16 @@ class UrDriver
880880
secondary_stream_->close();
881881
}
882882

883+
bool isReverseInterfaceConnected() const
884+
{
885+
return reverse_interface_->isConnected();
886+
}
887+
888+
bool isTrajectoryInterfaceConnected() const
889+
{
890+
return trajectory_interface_->isConnected();
891+
}
892+
883893
private:
884894
void init(const UrDriverConfiguration& config);
885895

src/control/trajectory_point_interface.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#include <ur_client_library/exceptions.h>
3131
#include <math.h>
3232
#include <stdexcept>
33+
#include "ur_client_library/comm/socket_t.h"
3334

3435
namespace urcl
3536
{
@@ -217,11 +218,11 @@ void TrajectoryPointInterface::connectionCallback(const socket_t filedescriptor)
217218
void TrajectoryPointInterface::disconnectionCallback(const socket_t filedescriptor)
218219
{
219220
URCL_LOG_DEBUG("Connection to trajectory interface dropped.");
220-
client_fd_ = -1;
221221
if (disconnection_callback_ != nullptr)
222222
{
223223
disconnection_callback_(filedescriptor);
224224
}
225+
client_fd_ = INVALID_SOCKET;
225226
}
226227

227228
void TrajectoryPointInterface::messageCallback(const socket_t filedescriptor, char* buffer, int nbytesrecv)

src/ur/instruction_executor.cpp

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,17 +29,23 @@
2929
// -- END LICENSE BLOCK ------------------------------------------------
3030

3131
#include "ur_client_library/ur/instruction_executor.h"
32+
#include <mutex>
3233
#include "ur_client_library/control/trajectory_point_interface.h"
34+
#include "ur_client_library/log.h"
35+
#include "ur_client_library/ur/robot_receive_timeout.h"
3336
void urcl::InstructionExecutor::trajDoneCallback(const urcl::control::TrajectoryResult& result)
3437
{
38+
URCL_LOG_DEBUG("Trajectory result received: %s", control::trajectoryResultToString(result).c_str());
3539
std::unique_lock<std::mutex> lock(trajectory_result_mutex_);
40+
trajectory_done_cv_.notify_all();
3641
trajectory_result_ = result;
3742
trajectory_running_ = false;
3843
}
3944
void urcl::InstructionExecutor::trajDisconnectCallback(const int filedescriptor)
4045
{
4146
URCL_LOG_INFO("Trajectory disconnect");
4247
std::unique_lock<std::mutex> lock(trajectory_result_mutex_);
48+
trajectory_done_cv_.notify_all();
4349
if (trajectory_running_)
4450
{
4551
trajectory_result_ = urcl::control::TrajectoryResult::TRAJECTORY_RESULT_FAILURE;
@@ -86,17 +92,20 @@ bool urcl::InstructionExecutor::executeMotion(
8692
}
8793
}
8894
trajectory_running_ = true;
95+
cancel_requested_ = false;
8996

90-
while (trajectory_running_)
97+
while (trajectory_running_ && !cancel_requested_)
9198
{
92-
std::this_thread::sleep_for(std::chrono::milliseconds(100));
9399
driver_->writeTrajectoryControlMessage(urcl::control::TrajectoryControlMessage::TRAJECTORY_NOOP);
100+
std::this_thread::sleep_for(std::chrono::milliseconds(100));
94101
}
102+
if (!cancel_requested_)
95103
{
96104
std::unique_lock<std::mutex> lock(trajectory_result_mutex_);
97105
URCL_LOG_INFO("Trajectory done with result %s", control::trajectoryResultToString(trajectory_result_).c_str());
98106
return trajectory_result_ == urcl::control::TrajectoryResult::TRAJECTORY_RESULT_SUCCESS;
99107
}
108+
return false;
100109
}
101110
bool urcl::InstructionExecutor::moveJ(const urcl::vector6d_t& target, const double acceleration, const double velocity,
102111
const double time, const double blend_radius)
@@ -110,3 +119,26 @@ bool urcl::InstructionExecutor::moveL(const urcl::Pose& target, const double acc
110119
return executeMotion({ std::make_shared<control::MoveLPrimitive>(
111120
target, blend_radius, std::chrono::milliseconds(static_cast<int>(time * 1000)), acceleration, velocity) });
112121
}
122+
123+
bool urcl::InstructionExecutor::cancelMotion()
124+
{
125+
cancel_requested_ = true;
126+
if (trajectory_running_)
127+
{
128+
URCL_LOG_INFO("Cancel motion");
129+
driver_->writeTrajectoryControlMessage(urcl::control::TrajectoryControlMessage::TRAJECTORY_CANCEL, -1,
130+
RobotReceiveTimeout::millisec(2000));
131+
std::unique_lock<std::mutex> lock(trajectory_result_mutex_);
132+
if (trajectory_done_cv_.wait_for(lock, std::chrono::milliseconds(200)) == std::cv_status::no_timeout)
133+
{
134+
return trajectory_result_ == urcl::control::TrajectoryResult::TRAJECTORY_RESULT_CANCELED;
135+
}
136+
else
137+
{
138+
URCL_LOG_ERROR("Sent a canceling request to the robot but waiting for the answer timed out.");
139+
return false;
140+
}
141+
}
142+
URCL_LOG_WARN("Canceling motion requested without a motion running.");
143+
return false;
144+
}
8 Bytes
Binary file not shown.

tests/test_instruction_executor.cpp

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
// -- END LICENSE BLOCK ------------------------------------------------
3030

3131
#include <gtest/gtest.h>
32+
#include <algorithm>
3233
#include <iostream>
3334
#include <thread>
3435
#include "ur_client_library/ur/instruction_executor.h"
@@ -70,6 +71,21 @@ class InstructionExecutorTest : public ::testing::Test
7071
ASSERT_TRUE(g_my_robot->waitForProgramRunning());
7172
}
7273
}
74+
void TearDown() override
75+
{
76+
g_my_robot->ur_driver_->stopControl();
77+
g_my_robot->waitForProgramNotRunning(1000);
78+
while (g_my_robot->ur_driver_->isTrajectoryInterfaceConnected())
79+
{
80+
std::this_thread::sleep_for(std::chrono::milliseconds(1));
81+
}
82+
URCL_LOG_INFO("Stopped robot control.");
83+
}
84+
85+
static void TearDownTestSuite()
86+
{
87+
g_my_robot.reset();
88+
}
7389
};
7490

7591
TEST_F(InstructionExecutorTest, execute_motion_sequence_success)
@@ -219,6 +235,69 @@ TEST_F(InstructionExecutorTest, unfeasible_movej_target_results_in_failure)
219235
ASSERT_FALSE(executor_->moveJ({ -123, 0, 0, 0, 0, 0 }));
220236
}
221237

238+
TEST_F(InstructionExecutorTest, canceling_without_running_trajectory_returns_false)
239+
{
240+
ASSERT_FALSE(executor_->isTrajectoryRunning());
241+
ASSERT_FALSE(executor_->cancelMotion());
242+
}
243+
244+
TEST(InstructionExecutorTestStandalone, canceling_without_receiving_answer_returns_false)
245+
{
246+
std::ifstream in_file(SCRIPT_FILE);
247+
std::ofstream out_file;
248+
const std::string test_script_file = "test_script.urscript";
249+
out_file.open(test_script_file);
250+
251+
std::string line;
252+
std::string pattern = "socket_send_int(TRAJECTORY_RESULT_CANCELED, \"trajectory_socket\")";
253+
while (std::getline(in_file, line))
254+
{
255+
if (line.find(pattern) == std::string::npos)
256+
{
257+
out_file << line << std::endl;
258+
}
259+
}
260+
out_file.close();
261+
auto my_robot = std::make_unique<ExampleRobotWrapper>(ROBOT_IP, OUTPUT_RECIPE, INPUT_RECIPE, g_HEADLESS,
262+
"external_control.urp", test_script_file);
263+
auto executor = std::make_unique<InstructionExecutor>(my_robot->ur_driver_);
264+
my_robot->clearProtectiveStop();
265+
// Make sure script is running on the robot
266+
if (!my_robot->waitForProgramRunning())
267+
{
268+
my_robot->resendRobotProgram();
269+
ASSERT_TRUE(my_robot->waitForProgramRunning());
270+
}
271+
ASSERT_TRUE(executor->moveJ({ -1.59, -1.72, -2.2, -0.8, 1.6, 0.2 }, 2.0, 2.0));
272+
std::thread move_thread([&executor]() { executor->moveJ({ -1.57, -1.6, 1.6, -0.7, 0.7, 2.7 }, 0.1, 0.1); });
273+
bool is_trajectory_running = false;
274+
std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
275+
while (!is_trajectory_running || std::chrono::steady_clock::now() > start + std::chrono::seconds(5))
276+
{
277+
is_trajectory_running = executor->isTrajectoryRunning();
278+
std::this_thread::sleep_for(std::chrono::milliseconds(100));
279+
}
280+
ASSERT_TRUE(executor->isTrajectoryRunning());
281+
ASSERT_FALSE(executor->cancelMotion());
282+
move_thread.join();
283+
std::remove(test_script_file.c_str());
284+
}
285+
286+
TEST_F(InstructionExecutorTest, canceling_with_running_trajectory_succeeds)
287+
{
288+
ASSERT_TRUE(executor_->moveJ({ -1.59, -1.72, -2.2, -0.8, 1.6, 0.2 }, 2.0, 2.0));
289+
std::thread move_thread([this]() { executor_->moveJ({ -1.57, -1.6, 1.6, -0.7, 0.7, 2.7 }); });
290+
bool is_trajectory_running = false;
291+
std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
292+
while (!is_trajectory_running || std::chrono::steady_clock::now() > start + std::chrono::seconds(5))
293+
{
294+
is_trajectory_running = executor_->isTrajectoryRunning();
295+
std::this_thread::sleep_for(std::chrono::milliseconds(100));
296+
}
297+
ASSERT_TRUE(executor_->cancelMotion());
298+
move_thread.join();
299+
}
300+
222301
TEST_F(InstructionExecutorTest, unfeasible_movel_target_results_in_failure)
223302
{
224303
// move to a feasible starting pose

0 commit comments

Comments
 (0)