Skip to content

Commit 873c8b0

Browse files
committed
feat: constrained freedrive implementation
1 parent 8f35234 commit 873c8b0

8 files changed

Lines changed: 150 additions & 34 deletions

File tree

examples/freedrive_example.cpp

Lines changed: 56 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -53,14 +53,44 @@ const std::string INPUT_RECIPE = "examples/resources/rtde_input_recipe.txt";
5353

5454
std::unique_ptr<ExampleRobotWrapper> g_my_robot;
5555

56-
void sendFreedriveMessageOrDie(const control::FreedriveControlMessage freedrive_action)
56+
void runFreedrive(UrDriver& urdriver, std::chrono::seconds duration)
5757
{
58-
bool ret = g_my_robot->getUrDriver()->writeFreedriveControlMessage(freedrive_action);
59-
if (!ret)
58+
URCL_LOG_INFO("Starting freedrive mode");
59+
60+
urdriver.writeFreedriveControlMessage(control::FreedriveControlMessage::FREEDRIVE_START);
61+
62+
auto start = std::chrono::steady_clock::now();
63+
while (std::chrono::steady_clock::now() - start < duration || duration.count() == 0)
64+
{
65+
// Keeping the robot in freedrive by sending NOOP messages
66+
urdriver.writeFreedriveControlMessage(control::FreedriveControlMessage::FREEDRIVE_NOOP);
67+
std::this_thread::sleep_for(std::chrono::milliseconds(2));
68+
}
69+
70+
urdriver.writeFreedriveControlMessage(control::FreedriveControlMessage::FREEDRIVE_STOP);
71+
URCL_LOG_INFO("Stopping freedrive mode");
72+
}
73+
74+
void runConstrainedFreedrive(UrDriver& urdriver, std::array<int32_t, 6>& free_axes, std::array<double, 6>& feature_pose,
75+
std::chrono::seconds duration)
76+
{
77+
URCL_LOG_INFO("Starting constrained freedrive mode");
78+
79+
urdriver.writeConstrainedFreedriveControlMessage(control::FreedriveControlMessage::FREEDRIVE_START, free_axes,
80+
feature_pose);
81+
82+
auto start = std::chrono::steady_clock::now();
83+
while (std::chrono::steady_clock::now() - start < duration || duration.count() == 0)
6084
{
61-
URCL_LOG_ERROR("Could not send joint command. Is there an external_control program running on the robot?");
62-
exit(1);
85+
// Keeping the robot in constrained freedrive by sending NOOP messages
86+
urdriver.writeConstrainedFreedriveControlMessage(control::FreedriveControlMessage::FREEDRIVE_NOOP, free_axes,
87+
feature_pose);
88+
std::this_thread::sleep_for(std::chrono::milliseconds(2));
6389
}
90+
91+
urdriver.writeConstrainedFreedriveControlMessage(control::FreedriveControlMessage::FREEDRIVE_STOP, free_axes,
92+
feature_pose);
93+
URCL_LOG_INFO("Stopping constrained freedrive mode");
6494
}
6595

6696
int main(int argc, char* argv[])
@@ -73,15 +103,26 @@ int main(int argc, char* argv[])
73103
robot_ip = std::string(argv[1]);
74104
}
75105

106+
// Select the freedrive mode
107+
bool constrained = false;
108+
if (argc > 2)
109+
{
110+
std::string arg = argv[2];
111+
constrained = (arg == "true" || arg == "1");
112+
}
113+
76114
// Parse how many seconds to run
77115
auto second_to_run = std::chrono::seconds(0);
78-
if (argc > 2)
116+
if (argc > 3)
79117
{
80-
second_to_run = std::chrono::seconds(std::stoi(argv[2]));
118+
second_to_run = std::chrono::seconds(std::stoi(argv[3]));
81119
}
82120

83-
bool headless_mode = true;
121+
std::array<int32_t, 6> free_axes = { 1, 0, 1, 1, 0, 1 };
122+
std::array<double, 6> feature_pose = { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 };
84123

124+
// Initialize robot connection
125+
bool headless_mode = true;
85126
g_my_robot = std::make_unique<ExampleRobotWrapper>(robot_ip, OUTPUT_RECIPE, INPUT_RECIPE, headless_mode,
86127
"external_control.urp");
87128

@@ -91,24 +132,13 @@ int main(int argc, char* argv[])
91132
return 1;
92133
}
93134

94-
URCL_LOG_INFO("Starting freedrive mode");
95-
sendFreedriveMessageOrDie(control::FreedriveControlMessage::FREEDRIVE_START);
96-
97-
std::chrono::duration<double> time_done(0);
98-
std::chrono::duration<double> timeout(second_to_run);
99-
auto stopwatch_last = std::chrono::steady_clock::now();
100-
auto stopwatch_now = stopwatch_last;
101-
102-
while (time_done < timeout || second_to_run.count() == 0)
135+
// Execute selected freedrive mode
136+
if (constrained)
103137
{
104-
sendFreedriveMessageOrDie(control::FreedriveControlMessage::FREEDRIVE_NOOP);
105-
106-
stopwatch_now = std::chrono::steady_clock::now();
107-
time_done += stopwatch_now - stopwatch_last;
108-
stopwatch_last = stopwatch_now;
109-
std::this_thread::sleep_for(std::chrono::milliseconds(2));
138+
runConstrainedFreedrive(*g_my_robot->getUrDriver(), free_axes, feature_pose, second_to_run);
139+
}
140+
else
141+
{
142+
runFreedrive(*g_my_robot->getUrDriver(), second_to_run);
110143
}
111-
112-
URCL_LOG_INFO("Stopping freedrive mode");
113-
sendFreedriveMessageOrDie(control::FreedriveControlMessage::FREEDRIVE_STOP);
114144
}

include/ur_client_library/control/reverse_interface.h

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,12 +140,18 @@ class ReverseInterface
140140
* \param robot_receive_timeout The read timeout configuration for the reverse socket running in the external
141141
* control script on the robot. If you want to make the read function blocking then use RobotReceiveTimeout::off()
142142
* function to create the RobotReceiveTimeout object
143+
* \param free_axes A 6-dimensional vector (x, y, z, rx, ry, rz) defining which axes are compliant.
144+
* Use 1 to enable movement in an axis and 0 to lock it.
145+
* \param feature_pose A pose vector [x, y, z, rx, ry, rz] defining the freedrive frame relative to the base frame.
146+
* Position values (x, y, z) should be in meters, and orientation values (rx, ry, rz) in radians.
143147
*
144148
* \returns True, if the write was performed successfully, false otherwise.
145149
*/
146150
bool
147151
writeFreedriveControlMessage(const FreedriveControlMessage freedrive_action,
148-
const RobotReceiveTimeout& robot_receive_timeout = RobotReceiveTimeout::millisec(200));
152+
const RobotReceiveTimeout& robot_receive_timeout = RobotReceiveTimeout::millisec(200),
153+
const std::array<int32_t, 6>& free_axes = { 1, 1, 1, 1, 1, 1 },
154+
const std::array<double, 6>& feature_pose = { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 });
149155

150156
/*!
151157
* \brief Set the Keepalive count. This will set the number of allowed timeout reads on the robot.
@@ -228,7 +234,7 @@ class ReverseInterface
228234
return s;
229235
}
230236

231-
static const int MAX_MESSAGE_LENGTH = 8;
237+
static const int MAX_MESSAGE_LENGTH = 15;
232238

233239
std::function<void(bool)> handle_program_state_;
234240
std::chrono::milliseconds step_time_;

include/ur_client_library/ur/ur_driver.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -611,6 +611,26 @@ class UrDriver
611611
writeFreedriveControlMessage(const control::FreedriveControlMessage freedrive_action,
612612
const RobotReceiveTimeout& robot_receive_timeout = RobotReceiveTimeout::millisec(200));
613613

614+
/*!
615+
* \brief Writes a control message in freedrive mode.
616+
*
617+
* \param freedrive_action The action to be taken, such as starting or stopping freedrive
618+
* \param free_axes A 6-dimensional vector (x, y, z, rx, ry, rz) defining which axes are compliant.
619+
* Use 1 to enable movement in an axis and 0 to lock it.
620+
* \param feature_pose A pose vector [x, y, z, rx, ry, rz] defining the freedrive frame relative to the base frame.
621+
* Position values (x, y, z) should be in meters, and orientation values (rx, ry, rz) in radians.
622+
* \param robot_receive_timeout The read timeout configuration for the reverse socket running in the external
623+
* control script on the robot. If you want to make the read function blocking then use RobotReceiveTimeout::off()
624+
* function to create the RobotReceiveTimeout object
625+
*
626+
* \returns True on successful write.
627+
*/
628+
bool writeConstrainedFreedriveControlMessage(
629+
const control::FreedriveControlMessage freedrive_action,
630+
const std::array<int32_t, 6>& free_axes = { 1, 1, 1, 1, 1, 1 },
631+
const std::array<double, 6>& feature_pose = { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 },
632+
const RobotReceiveTimeout& robot_receive_timeout = RobotReceiveTimeout::millisec(200));
633+
614634
/*!
615635
* \brief Zero the force torque sensor (only availbe on e-Series). Note: It requires the external control script to
616636
* be running or the robot to be in headless mode

resources/external_control.urscript

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ MODE_FREEDRIVE = 6
2929
MODE_TOOL_IN_CONTACT = 7
3030
MODE_TORQUE = 8
3131
# Data dimensions of the message received on the reverse interface
32-
REVERSE_INTERFACE_DATA_DIMENSION = 8
32+
REVERSE_INTERFACE_DATA_DIMENSION = 15
3333

3434
TRAJECTORY_MODE_RECEIVE = 1
3535
TRAJECTORY_MODE_CANCEL = -1
@@ -1142,7 +1142,9 @@ while control_mode > MODE_STOPPED:
11421142
elif control_mode == MODE_FREEDRIVE:
11431143
if params_mult[2] == FREEDRIVE_MODE_START:
11441144
textmsg("Entering freedrive mode")
1145-
freedrive_mode()
1145+
free_axes = [params_mult[3], params_mult[4], params_mult[5], params_mult[6], params_mult[7], params_mult[8]]
1146+
feature_pose = p[params_mult[9]/MULT_jointstate, params_mult[10]/MULT_jointstate, params_mult[11]/MULT_jointstate, params_mult[12]/MULT_jointstate, params_mult[13]/MULT_jointstate, params_mult[14]/MULT_jointstate]
1147+
freedrive_mode(free_axes, feature_pose)
11461148
elif params_mult[2] == FREEDRIVE_MODE_STOP:
11471149
textmsg("Leaving freedrive mode")
11481150
end_freedrive_mode()

src/control/reverse_interface.cpp

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -169,9 +169,11 @@ bool ReverseInterface::writeTrajectoryControlMessage(const TrajectoryControlMess
169169
}
170170

171171
bool ReverseInterface::writeFreedriveControlMessage(const FreedriveControlMessage freedrive_action,
172-
const RobotReceiveTimeout& robot_receive_timeout)
172+
const RobotReceiveTimeout& robot_receive_timeout,
173+
const std::array<int32_t, 6>& free_axes,
174+
const std::array<double, 6>& feature_pose)
173175
{
174-
const int message_length = 2;
176+
const int message_length = 14;
175177
if (client_fd_ == INVALID_SOCKET)
176178
{
177179
return false;
@@ -197,6 +199,20 @@ bool ReverseInterface::writeFreedriveControlMessage(const FreedriveControlMessag
197199
val = htobe32(toUnderlying(freedrive_action));
198200
b_pos += append(b_pos, val);
199201

202+
// Add allowed axes for movement
203+
for (int32_t axis : free_axes)
204+
{
205+
val = htobe32(axis);
206+
b_pos += append(b_pos, val);
207+
}
208+
209+
// Add feature pose scaled to microns microradians to avoid precision loss in integer conversion
210+
for (double p : feature_pose)
211+
{
212+
val = htobe32(static_cast<int32_t>(p * MULT_JOINTSTATE));
213+
b_pos += append(b_pos, val);
214+
}
215+
200216
// writing zeros to allow usage with other script commands
201217
for (size_t i = message_length; i < MAX_MESSAGE_LENGTH - 1; i++)
202218
{

src/ur/ur_driver.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,15 @@ bool UrDriver::writeFreedriveControlMessage(const control::FreedriveControlMessa
266266
return reverse_interface_->writeFreedriveControlMessage(freedrive_action, robot_receive_timeout);
267267
}
268268

269+
bool UrDriver::writeConstrainedFreedriveControlMessage(const control::FreedriveControlMessage freedrive_action,
270+
const std::array<int32_t, 6>& free_axes,
271+
const std::array<double, 6>& feature_pose,
272+
const RobotReceiveTimeout& robot_receive_timeout)
273+
{
274+
return reverse_interface_->writeFreedriveControlMessage(freedrive_action, robot_receive_timeout, free_axes,
275+
feature_pose);
276+
}
277+
269278
bool UrDriver::zeroFTSensor()
270279
{
271280
if (getVersion().major < 5)

tests/test_reverse_interface.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,22 @@ TEST_F(ReverseInterfaceTest, write_freedrive_control_message)
478478
EXPECT_EQ(toUnderlying(written_freedrive_message), received_freedrive_message);
479479
}
480480

481+
TEST_F(ReverseInterfaceTest, write_constrained_freedrive_message)
482+
{
483+
// Wait for the client to connect to the server
484+
EXPECT_TRUE(waitForProgramState(1000, true));
485+
control::FreedriveControlMessage written_freedrive_message = control::FreedriveControlMessage::FREEDRIVE_START;
486+
487+
std::array<int32_t, 6> free_axes = { 1, 0, 1, 0, 1, 0 };
488+
std::array<double, 6> feature_pos = { 0.1, -0.2, 0.5, 1.1, 0.0, 3.14 };
489+
490+
reverse_interface_->writeFreedriveControlMessage(written_freedrive_message, RobotReceiveTimeout::millisec(200),
491+
free_axes, feature_pos);
492+
493+
int32_t received_freedrive_message = client_->getFreedriveControlMode();
494+
EXPECT_EQ(toUnderlying(written_freedrive_message), received_freedrive_message);
495+
}
496+
481497
TEST_F(ReverseInterfaceTest, deprecated_set_keep_alive_count)
482498
{
483499
// Wait for the client to connect to the server

tests/test_ur_driver.cpp

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,8 +155,11 @@ TEST_F(UrDriverTest, read_existing_script_file)
155155
TEST_F(UrDriverTest, robot_receive_timeout)
156156
{
157157
// Robot program should time out after the robot receive timeout, whether it takes exactly 200 ms is not so important
158-
vector6d_t zeros = { 0, 0, 0, 0, 0, 0 };
159-
g_my_robot->getUrDriver()->writeJointCommand(zeros, comm::ControlMode::MODE_IDLE, RobotReceiveTimeout::millisec(200));
158+
std::array<int32_t, 6> zeros_int = { 0, 0, 0, 0, 0, 0 };
159+
std::array<double, 6> zeros_double = { 0, 0, 0, 0, 0, 0 };
160+
161+
g_my_robot->getUrDriver()->writeJointCommand(zeros_double, comm::ControlMode::MODE_IDLE,
162+
RobotReceiveTimeout::millisec(200));
160163
EXPECT_TRUE(g_my_robot->waitForProgramNotRunning(400));
161164

162165
// Start robot program
@@ -172,6 +175,15 @@ TEST_F(UrDriverTest, robot_receive_timeout)
172175
g_my_robot->resendRobotProgram();
173176
EXPECT_TRUE(g_my_robot->waitForProgramRunning(1000));
174177

178+
// Robot program should time out after the robot receive timeout, whether it takes exactly 200 ms is not so important
179+
g_my_robot->getUrDriver()->writeConstrainedFreedriveControlMessage(
180+
control::FreedriveControlMessage::FREEDRIVE_NOOP, zeros_int, zeros_double, RobotReceiveTimeout::millisec(200));
181+
EXPECT_TRUE(g_my_robot->waitForProgramNotRunning(400));
182+
183+
// Start robot program
184+
g_my_robot->resendRobotProgram();
185+
EXPECT_TRUE(g_my_robot->waitForProgramRunning(1000));
186+
175187
// Robot program should time out after the robot receive timeout, whether it takes exactly 200 ms is not so important
176188
g_my_robot->getUrDriver()->writeTrajectoryControlMessage(control::TrajectoryControlMessage::TRAJECTORY_NOOP, -1,
177189
RobotReceiveTimeout::millisec(200));
@@ -197,6 +209,11 @@ TEST_F(UrDriverTest, robot_receive_timeout_off)
197209
RobotReceiveTimeout::off());
198210
EXPECT_FALSE(g_my_robot->waitForProgramNotRunning(1000));
199211

212+
g_my_robot->getUrDriver()->writeConstrainedFreedriveControlMessage(control::FreedriveControlMessage::FREEDRIVE_NOOP,
213+
{ 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0 },
214+
RobotReceiveTimeout::off());
215+
EXPECT_FALSE(g_my_robot->waitForProgramNotRunning(1000));
216+
200217
// Program should keep running when setting receive timeout off
201218
g_my_robot->getUrDriver()->writeTrajectoryControlMessage(control::TrajectoryControlMessage::TRAJECTORY_NOOP, -1,
202219
RobotReceiveTimeout::off());

0 commit comments

Comments
 (0)