Skip to content
This repository is currently being migrated. It's locked while the migration is in progress.

Commit a15af07

Browse files
tylerjwAndyZe
andauthored
[feature] Low latency mode (moveit#2401)
Co-authored-by: AndyZe <andyz@utexas.edu>
1 parent 7f647b0 commit a15af07

10 files changed

+420
-41
lines changed

moveit_ros/moveit_servo/CMakeLists.txt

+10
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,16 @@ if(CATKIN_ENABLE_TESTING)
175175
${catkin_LIBRARIES}
176176
)
177177

178+
# low_latency
179+
add_rostest_gtest(servo_low_latency_test
180+
test/servo_low_latency_test.test
181+
test/servo_low_latency_test.cpp
182+
)
183+
target_link_libraries(servo_low_latency_test
184+
${SERVO_LIB_NAME}
185+
${catkin_LIBRARIES}
186+
)
187+
178188
# pose_tracking
179189
add_rostest_gtest(pose_tracking_test
180190
test/pose_tracking_test.test

moveit_ros/moveit_servo/config/ur_simulated_config.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ low_pass_filter_coeff: 2. # Larger --> trust the filtered data more, trust the
1515

1616
## Properties of outgoing commands
1717
publish_period: 0.008 # 1/Nominal publish rate [seconds]
18+
low_latency_mode: false # Set this to true to publish as soon as an incoming Twist command is received (publish_period is ignored)
1819

1920
# What type of topic does your robot driver expect?
2021
# Currently supported are std_msgs/Float64MultiArray (for ros_control JointGroupVelocityController or JointGroupPositionController)

moveit_ros/moveit_servo/include/moveit_servo/servo_calcs.h

+21-10
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@
3939
#pragma once
4040

4141
// C++
42+
#include <condition_variable>
4243
#include <mutex>
44+
#include <thread>
4345

4446
// ROS
4547
#include <control_msgs/JointJog.h>
@@ -69,10 +71,7 @@ class ServoCalcs
6971
ServoCalcs(ros::NodeHandle& nh, ServoParameters& parameters,
7072
const planning_scene_monitor::PlanningSceneMonitorPtr& planning_scene_monitor);
7173

72-
~ServoCalcs()
73-
{
74-
timer_.stop();
75-
}
74+
~ServoCalcs();
7675

7776
/** \brief Start the timer where we do work and publish outputs */
7877
void start();
@@ -106,8 +105,14 @@ class ServoCalcs
106105
void changeRobotLinkCommandFrame(const std::string& new_command_frame);
107106

108107
private:
109-
/** \brief Timer method */
110-
void run(const ros::TimerEvent& timer_event);
108+
/** \brief Run the main calculation loop */
109+
void mainCalcLoop();
110+
111+
/** \brief Do calculations for a single iteration. Publish one outgoing command */
112+
void calculateSingleIteration();
113+
114+
/** \brief Stop the currently running thread */
115+
void stop();
111116

112117
/** \brief Do servoing calculations for Cartesian twist commands. */
113118
bool cartesianServoCalcs(geometry_msgs::TwistStamped& cmd, trajectory_msgs::JointTrajectory& joint_trajectory);
@@ -257,8 +262,6 @@ class ServoCalcs
257262
trajectory_msgs::JointTrajectoryConstPtr last_sent_command_;
258263

259264
// ROS
260-
ros::Timer timer_;
261-
ros::Duration period_;
262265
ros::Subscriber joint_state_sub_;
263266
ros::Subscriber twist_stamped_sub_;
264267
ros::Subscriber joint_cmd_sub_;
@@ -270,6 +273,10 @@ class ServoCalcs
270273
ros::ServiceServer control_dimensions_server_;
271274
ros::ServiceServer reset_servo_status_;
272275

276+
// Main tracking / result publisher loop
277+
std::thread thread_;
278+
bool stop_requested_;
279+
273280
// Status
274281
StatusCode status_ = StatusCode::NO_WARNING;
275282
bool paused_ = false;
@@ -292,8 +299,8 @@ class ServoCalcs
292299
// The dimesions to control. In the command frame. [x, y, z, roll, pitch, yaw]
293300
std::array<bool, 6> control_dimensions_ = { { true, true, true, true, true, true } };
294301

295-
// latest_state_mutex_ is used to protect the state below it
296-
mutable std::mutex latest_state_mutex_;
302+
// input_mutex_ is used to protect the state below it
303+
mutable std::mutex input_mutex_;
297304
Eigen::Isometry3d tf_moveit_to_robot_cmd_frame_;
298305
Eigen::Isometry3d tf_moveit_to_ee_frame_;
299306
geometry_msgs::TwistStampedConstPtr latest_twist_stamped_;
@@ -302,5 +309,9 @@ class ServoCalcs
302309
ros::Time latest_joint_command_stamp_ = ros::Time(0.);
303310
bool latest_nonzero_twist_stamped_ = false;
304311
bool latest_nonzero_joint_cmd_ = false;
312+
313+
// input condition variable used for low latency mode
314+
std::condition_variable input_cv_;
315+
bool new_input_cmd_ = false;
305316
};
306317
} // namespace moveit_servo

moveit_ros/moveit_servo/include/moveit_servo/servo_parameters.h

+1
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ struct ServoParameters
7171
bool publish_joint_positions;
7272
bool publish_joint_velocities;
7373
bool publish_joint_accelerations;
74+
bool low_latency_mode;
7475
// Collision checking
7576
bool check_collisions;
7677
std::string collision_check_type;

moveit_ros/moveit_servo/src/servo.cpp

+12-1
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ bool Servo::readParameters()
136136
// 'collision_proximity_threshold' parameter was removed, replaced with separate self- and scene-collision proximity
137137
// thresholds
138138
// TODO(JStech): remove this deprecation warning in ROS Noetic; simplify error case handling
139-
if (nh_.hasParam("collision_proximity_threshold") &&
139+
if (nh.hasParam("collision_proximity_threshold") &&
140140
rosparam_shortcuts::get(LOGNAME, nh, "collision_proximity_threshold", collision_proximity_threshold))
141141
{
142142
ROS_WARN_NAMED(LOGNAME, "'collision_proximity_threshold' parameter is deprecated, and has been replaced by separate"
@@ -170,6 +170,17 @@ bool Servo::readParameters()
170170
error += !rosparam_shortcuts::get(LOGNAME, nh, "warning_topic", parameters_.status_topic);
171171
}
172172

173+
if (nh.hasParam("low_latency_mode"))
174+
{
175+
error += !rosparam_shortcuts::get(LOGNAME, nh, "low_latency_mode", parameters_.low_latency_mode);
176+
}
177+
else
178+
{
179+
ROS_WARN_NAMED(LOGNAME, "'low_latency_mode' is a new parameter that runs servo calc immediately after receiving "
180+
"input. Setting to the default value of false.");
181+
parameters_.low_latency_mode = false;
182+
}
183+
173184
rosparam_shortcuts::shutdownIfError(LOGNAME, error);
174185

175186
// Input checking

moveit_ros/moveit_servo/src/servo_calcs.cpp

+103-30
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ geometry_msgs::TransformStamped convertIsometryToTransform(const Eigen::Isometry
8686
// Constructor for the class that handles servoing calculations
8787
ServoCalcs::ServoCalcs(ros::NodeHandle& nh, ServoParameters& parameters,
8888
const planning_scene_monitor::PlanningSceneMonitorPtr& planning_scene_monitor)
89-
: nh_(nh), parameters_(parameters), planning_scene_monitor_(planning_scene_monitor), period_(parameters.publish_period)
89+
: nh_(nh), parameters_(parameters), planning_scene_monitor_(planning_scene_monitor), stop_requested_(true)
9090
{
9191
// MoveIt Setup
9292
current_state_ = planning_scene_monitor_->getStateMonitor()->getCurrentState();
@@ -150,8 +150,16 @@ ServoCalcs::ServoCalcs(ros::NodeHandle& nh, ServoParameters& parameters,
150150
tf_moveit_to_robot_cmd_frame_ = empty_matrix;
151151
}
152152

153+
ServoCalcs::~ServoCalcs()
154+
{
155+
stop();
156+
}
157+
153158
void ServoCalcs::start()
154159
{
160+
// Stop the thread if we are currently running
161+
stop();
162+
155163
// We will update last_sent_command_ every time we start servo
156164
auto initial_joint_trajectory = moveit::util::make_shared_from_pool<trajectory_msgs::JointTrajectory>();
157165

@@ -188,19 +196,78 @@ void ServoCalcs::start()
188196
tf_moveit_to_robot_cmd_frame_ = current_state_->getGlobalLinkTransform(parameters_.planning_frame).inverse() *
189197
current_state_->getGlobalLinkTransform(parameters_.robot_link_command_frame);
190198

191-
timer_ = nh_.createTimer(period_, &ServoCalcs::run, this);
199+
stop_requested_ = false;
200+
thread_ = std::thread([this] { mainCalcLoop(); });
201+
new_input_cmd_ = false;
192202
}
193203

194-
void ServoCalcs::run(const ros::TimerEvent& timer_event)
204+
void ServoCalcs::stop()
195205
{
196-
// Log warning when the last loop duration was longer than the period
197-
if (timer_event.profile.last_duration.toSec() > period_.toSec())
206+
// Request stop
207+
stop_requested_ = true;
208+
209+
// Notify condition variable in case the thread is blocked on it
198210
{
199-
ROS_WARN_STREAM_THROTTLE_NAMED(ROS_LOG_THROTTLE_PERIOD, LOGNAME,
200-
"last_duration: " << timer_event.profile.last_duration.toSec() << " ("
201-
<< period_.toSec() << ")");
211+
// scope so the mutex is unlocked after so the thread can continue
212+
// and therefore be joinable
213+
const std::lock_guard<std::mutex> lock(input_mutex_);
214+
new_input_cmd_ = false;
215+
input_cv_.notify_all();
216+
}
217+
218+
// Join the thread
219+
if (thread_.joinable())
220+
{
221+
thread_.join();
222+
}
223+
}
224+
225+
void ServoCalcs::mainCalcLoop()
226+
{
227+
ros::Rate rate(1.0 / parameters_.publish_period);
228+
229+
while (ros::ok() && !stop_requested_)
230+
{
231+
// lock the input state mutex
232+
std::unique_lock<std::mutex> input_lock(input_mutex_);
233+
234+
// low latency mode
235+
if (parameters_.low_latency_mode)
236+
{
237+
input_cv_.wait(input_lock, [this] { return (new_input_cmd_ || stop_requested_); });
238+
239+
// break out of the loop if stop was requested
240+
if (stop_requested_)
241+
break;
242+
}
243+
244+
// reset new_input_cmd_ flag
245+
new_input_cmd_ = false;
246+
247+
// run servo calcs
248+
const auto start_time = ros::Time::now();
249+
calculateSingleIteration();
250+
const auto run_duration = ros::Time::now() - start_time;
251+
252+
// Log warning when the run duration was longer than the period
253+
if (run_duration.toSec() > parameters_.publish_period)
254+
{
255+
ROS_WARN_STREAM_THROTTLE_NAMED(ROS_LOG_THROTTLE_PERIOD, LOGNAME,
256+
"run_duration: " << run_duration.toSec() << " (" << parameters_.publish_period
257+
<< ")");
258+
}
259+
260+
// normal mode, unlock input mutex and wait for the period of the loop
261+
if (!parameters_.low_latency_mode)
262+
{
263+
input_lock.unlock();
264+
rate.sleep();
265+
}
202266
}
267+
}
203268

269+
void ServoCalcs::calculateSingleIteration()
270+
{
204271
// Publish status each loop iteration
205272
auto status_msg = moveit::util::make_shared_from_pool<std_msgs::Int8>();
206273
status_msg->data = static_cast<int8_t>(status_);
@@ -213,22 +280,20 @@ void ServoCalcs::run(const ros::TimerEvent& timer_event)
213280

214281
// Update from latest state
215282
current_state_ = planning_scene_monitor_->getStateMonitor()->getCurrentState();
216-
{
217-
const std::lock_guard<std::mutex> lock(latest_state_mutex_);
218-
if (latest_twist_stamped_)
219-
twist_stamped_cmd_ = *latest_twist_stamped_;
220-
if (latest_joint_cmd_)
221-
joint_servo_cmd_ = *latest_joint_cmd_;
222-
223-
// Check for stale cmds
224-
twist_command_is_stale_ =
225-
((ros::Time::now() - latest_twist_command_stamp_) >= ros::Duration(parameters_.incoming_command_timeout));
226-
joint_command_is_stale_ =
227-
((ros::Time::now() - latest_joint_command_stamp_) >= ros::Duration(parameters_.incoming_command_timeout));
228-
229-
have_nonzero_twist_stamped_ = latest_nonzero_twist_stamped_;
230-
have_nonzero_joint_command_ = latest_nonzero_joint_cmd_;
231-
}
283+
284+
if (latest_twist_stamped_)
285+
twist_stamped_cmd_ = *latest_twist_stamped_;
286+
if (latest_joint_cmd_)
287+
joint_servo_cmd_ = *latest_joint_cmd_;
288+
289+
// Check for stale cmds
290+
twist_command_is_stale_ =
291+
((ros::Time::now() - latest_twist_command_stamp_) >= ros::Duration(parameters_.incoming_command_timeout));
292+
joint_command_is_stale_ =
293+
((ros::Time::now() - latest_joint_command_stamp_) >= ros::Duration(parameters_.incoming_command_timeout));
294+
295+
have_nonzero_twist_stamped_ = latest_nonzero_twist_stamped_;
296+
have_nonzero_joint_command_ = latest_nonzero_joint_cmd_;
232297

233298
// Get the transform from MoveIt planning frame to servoing command frame
234299
// Calculate this transform to ensure it is available via C++ API
@@ -980,7 +1045,7 @@ void ServoCalcs::removeDimension(Eigen::MatrixXd& jacobian, Eigen::VectorXd& del
9801045

9811046
bool ServoCalcs::getCommandFrameTransform(Eigen::Isometry3d& transform)
9821047
{
983-
const std::lock_guard<std::mutex> lock(latest_state_mutex_);
1048+
const std::lock_guard<std::mutex> lock(input_mutex_);
9841049
transform = tf_moveit_to_robot_cmd_frame_;
9851050

9861051
// All zeros means the transform wasn't initialized, so return false
@@ -989,7 +1054,7 @@ bool ServoCalcs::getCommandFrameTransform(Eigen::Isometry3d& transform)
9891054

9901055
bool ServoCalcs::getCommandFrameTransform(geometry_msgs::TransformStamped& transform)
9911056
{
992-
const std::lock_guard<std::mutex> lock(latest_state_mutex_);
1057+
const std::lock_guard<std::mutex> lock(input_mutex_);
9931058
// All zeros means the transform wasn't initialized, so return false
9941059
if (tf_moveit_to_robot_cmd_frame_.matrix().isZero(0))
9951060
{
@@ -1003,7 +1068,7 @@ bool ServoCalcs::getCommandFrameTransform(geometry_msgs::TransformStamped& trans
10031068

10041069
bool ServoCalcs::getEEFrameTransform(Eigen::Isometry3d& transform)
10051070
{
1006-
const std::lock_guard<std::mutex> lock(latest_state_mutex_);
1071+
const std::lock_guard<std::mutex> lock(input_mutex_);
10071072
transform = tf_moveit_to_ee_frame_;
10081073

10091074
// All zeros means the transform wasn't initialized, so return false
@@ -1012,7 +1077,7 @@ bool ServoCalcs::getEEFrameTransform(Eigen::Isometry3d& transform)
10121077

10131078
bool ServoCalcs::getEEFrameTransform(geometry_msgs::TransformStamped& transform)
10141079
{
1015-
const std::lock_guard<std::mutex> lock(latest_state_mutex_);
1080+
const std::lock_guard<std::mutex> lock(input_mutex_);
10161081
// All zeros means the transform wasn't initialized, so return false
10171082
if (tf_moveit_to_ee_frame_.matrix().isZero(0))
10181083
{
@@ -1025,22 +1090,30 @@ bool ServoCalcs::getEEFrameTransform(geometry_msgs::TransformStamped& transform)
10251090

10261091
void ServoCalcs::twistStampedCB(const geometry_msgs::TwistStampedConstPtr& msg)
10271092
{
1028-
const std::lock_guard<std::mutex> lock(latest_state_mutex_);
1093+
const std::lock_guard<std::mutex> lock(input_mutex_);
10291094
latest_twist_stamped_ = msg;
10301095
latest_nonzero_twist_stamped_ = isNonZero(*latest_twist_stamped_);
10311096

10321097
if (msg->header.stamp != ros::Time(0.))
10331098
latest_twist_command_stamp_ = msg->header.stamp;
1099+
1100+
// notify that we have a new input
1101+
new_input_cmd_ = true;
1102+
input_cv_.notify_all();
10341103
}
10351104

10361105
void ServoCalcs::jointCmdCB(const control_msgs::JointJogConstPtr& msg)
10371106
{
1038-
const std::lock_guard<std::mutex> lock(latest_state_mutex_);
1107+
const std::lock_guard<std::mutex> lock(input_mutex_);
10391108
latest_joint_cmd_ = msg;
10401109
latest_nonzero_joint_cmd_ = isNonZero(*latest_joint_cmd_);
10411110

10421111
if (msg->header.stamp != ros::Time(0.))
10431112
latest_joint_command_stamp_ = msg->header.stamp;
1113+
1114+
// notify that we have a new input
1115+
new_input_cmd_ = true;
1116+
input_cv_.notify_all();
10441117
}
10451118

10461119
void ServoCalcs::collisionVelocityScaleCB(const std_msgs::Float64ConstPtr& msg)

moveit_ros/moveit_servo/test/config/servo_settings.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ scale:
1414
low_pass_filter_coeff: 2. # Larger --> trust the filtered data more, trust the measurements less.
1515

1616
## Properties of outgoing commands
17+
low_latency_mode: false # Set this to true to tie the output rate to the input rate
1718
publish_period: 0.01 # 1/Nominal publish rate [seconds]
1819

1920
# What type of topic does your robot driver expect?

0 commit comments

Comments
 (0)