Skip to content

Commit 0e7c260

Browse files
destoglv-lopez
andauthored
[JTC] Allow integration of states in goal trajectories (ros-controls#190)
* Add allow_integration flag * Added position and velocity deduction to trajectory. * Added support for deduction of states from their derivatives. * Apply suggestions from code review Co-authored-by: Victor Lopez <3469405+v-lopez@users.noreply.github.com> * Correct warnings and formating. * Update test to use more human-readable values. * Apply reviewers comments. * Last test fixes. Co-authored-by: Denis Štogl <destogl@users.noreply.github.com> Co-authored-by: Victor Lopez <3469405+v-lopez@users.noreply.github.com>
1 parent 649bdbe commit 0e7c260

File tree

6 files changed

+533
-29
lines changed

6 files changed

+533
-29
lines changed

‎joint_trajectory_controller/include/joint_trajectory_controller/joint_trajectory_controller.hpp‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,8 @@ class JointTrajectoryController : public controller_interface::ControllerInterfa
129129
/// This is useful when robot is not exactly following the commanded trajectory.
130130
bool open_loop_control_ = false;
131131
trajectory_msgs::msg::JointTrajectoryPoint last_commanded_state_;
132+
/// Allow integration in goal trajectories to accept goals without position or velocity specified
133+
bool allow_integration_in_goal_trajectories_ = false;
132134

133135
// The interfaces are defined as the types in 'allowed_interface_types_' member.
134136
// For convenience, for each type the interfaces are ordered so that i-th position

‎joint_trajectory_controller/include/joint_trajectory_controller/trajectory.hpp‎

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -58,18 +58,27 @@ class Trajectory
5858
/// Find the segment (made up of 2 points) and its expected state from the
5959
/// containing trajectory.
6060
/**
61-
* Specific case returns for start_segment_itr and end_segment_itr:
62-
* - Sampling before the trajectory start:
63-
* start_segment_itr = begin(), end_segment_itr = begin()
64-
* - Sampling exactly on a point of the trajectory:
65-
* start_segment_itr = iterator where point is, end_segment_itr = iterator after start_segment_itr
66-
* - Sampling between points:
67-
* start_segment_itr = iterator before the sampled point, end_segment_itr = iterator after start_segment_itr
68-
* - Sampling after entire trajectory:
69-
* start_segment_itr = --end(), end_segment_itr = end()
70-
* - Sampling empty msg or before the time given in set_point_before_trajectory_msg()
71-
* return false
72-
*/
61+
* Sampling trajectory at given \p sample_time.
62+
* If position in the \p end_segment_itr is missing it will be deduced from provided velocity, or acceleration respectively.
63+
* Deduction assumes that the provided velocity or acceleration have to be reached at the time defined in the segment.
64+
*
65+
* Specific case returns for start_segment_itr and end_segment_itr:
66+
* - Sampling before the trajectory start:
67+
* start_segment_itr = begin(), end_segment_itr = begin()
68+
* - Sampling exactly on a point of the trajectory:
69+
* start_segment_itr = iterator where point is, end_segment_itr = iterator after start_segment_itr
70+
* - Sampling between points:
71+
* start_segment_itr = iterator before the sampled point, end_segment_itr = iterator after start_segment_itr
72+
* - Sampling after entire trajectory:
73+
* start_segment_itr = --end(), end_segment_itr = end()
74+
* - Sampling empty msg or before the time given in set_point_before_trajectory_msg()
75+
* return false
76+
*
77+
* \param[in] sample_time Time at which trajectory will be sampled.
78+
* \param[out] expected_state Calculated new at \p sample_time.
79+
* \param[out] start_segment_itr Iterator to the start segment for given \p sample_time. See description above.
80+
* \param[out] end_segment_itr Iterator to the end segment for given \p sample_time. See description above.
81+
*/
7382
JOINT_TRAJECTORY_CONTROLLER_PUBLIC
7483
bool sample(
7584
const rclcpp::Time & sample_time, trajectory_msgs::msg::JointTrajectoryPoint & output_state,
@@ -125,6 +134,11 @@ class Trajectory
125134
bool is_sampled_already() const { return sampled_already_; }
126135

127136
private:
137+
void deduce_from_derivatives(
138+
trajectory_msgs::msg::JointTrajectoryPoint & first_state,
139+
trajectory_msgs::msg::JointTrajectoryPoint & second_state, const size_t dim,
140+
const double delta_t);
141+
128142
std::shared_ptr<trajectory_msgs::msg::JointTrajectory> trajectory_msg_;
129143
rclcpp::Time trajectory_start_time_;
130144

‎joint_trajectory_controller/src/joint_trajectory_controller.cpp‎

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ controller_interface::CallbackReturn JointTrajectoryController::on_init()
6262
auto_declare<double>("action_monitor_rate", 20.0);
6363
auto_declare<bool>("allow_partial_joints_goal", allow_partial_joints_goal_);
6464
auto_declare<bool>("open_loop_control", open_loop_control_);
65+
auto_declare<bool>(
66+
"allow_integration_in_goal_trajectories", allow_integration_in_goal_trajectories_);
6567
auto_declare<double>("constraints.stopped_velocity_tolerance", 0.01);
6668
auto_declare<double>("constraints.goal_time", 0.0);
6769
}
@@ -138,6 +140,7 @@ controller_interface::return_type JointTrajectoryController::update(
138140
{
139141
fill_partial_goal(*new_external_msg);
140142
sort_to_local_joint_order(*new_external_msg);
143+
// TODO(denis): Add here integration of position and velocity
141144
traj_external_point_ptr_->update(*new_external_msg);
142145
}
143146

@@ -178,7 +181,6 @@ controller_interface::return_type JointTrajectoryController::update(
178181

179182
// find segment for current timestamp
180183
TrajectoryPointConstIter start_segment_itr, end_segment_itr;
181-
// TODO(anyone): this is kind-of open-loop concept? I am right?
182184
const bool valid_point =
183185
(*traj_point_active_ptr_)->sample(time, state_desired, start_segment_itr, end_segment_itr);
184186

@@ -659,6 +661,8 @@ controller_interface::CallbackReturn JointTrajectoryController::on_configure(
659661

660662
// Read parameters customizing controller for special cases
661663
open_loop_control_ = node_->get_parameter("open_loop_control").get_value<bool>();
664+
allow_integration_in_goal_trajectories_ =
665+
node_->get_parameter("allow_integration_in_goal_trajectories").get_value<bool>();
662666

663667
// subscriber callback
664668
// non realtime
@@ -1205,7 +1209,27 @@ bool JointTrajectoryController::validate_trajectory_msg(
12051209

12061210
const size_t joint_count = trajectory.joint_names.size();
12071211
const auto & points = trajectory.points;
1208-
if (
1212+
// This currently supports only position, velocity and acceleration inputs
1213+
if (allow_integration_in_goal_trajectories_)
1214+
{
1215+
const bool all_empty = points[i].positions.empty() && points[i].velocities.empty() &&
1216+
points[i].accelerations.empty();
1217+
const bool position_error =
1218+
!points[i].positions.empty() &&
1219+
!validate_trajectory_point_field(joint_count, points[i].positions, "positions", i, false);
1220+
const bool velocity_error =
1221+
!points[i].velocities.empty() &&
1222+
!validate_trajectory_point_field(joint_count, points[i].velocities, "velocities", i, false);
1223+
const bool acceleration_error =
1224+
!points[i].accelerations.empty() &&
1225+
!validate_trajectory_point_field(
1226+
joint_count, points[i].accelerations, "accelerations", i, false);
1227+
if (all_empty || position_error || velocity_error || acceleration_error)
1228+
{
1229+
return false;
1230+
}
1231+
}
1232+
else if (
12091233
!validate_trajectory_point_field(joint_count, points[i].positions, "positions", i, false) ||
12101234
!validate_trajectory_point_field(joint_count, points[i].velocities, "velocities", i, true) ||
12111235
!validate_trajectory_point_field(

‎joint_trajectory_controller/src/trajectory.cpp‎

Lines changed: 55 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -89,16 +89,21 @@ bool Trajectory::sample(
8989
}
9090

9191
// current time hasn't reached traj time of the first point in the msg yet
92-
const auto & first_point_in_msg = trajectory_msg_->points[0];
93-
const rclcpp::Duration offset = first_point_in_msg.time_from_start;
94-
const rclcpp::Time first_point_timestamp = trajectory_start_time_ + offset;
92+
auto & first_point_in_msg = trajectory_msg_->points[0];
93+
const rclcpp::Time first_point_timestamp =
94+
trajectory_start_time_ + first_point_in_msg.time_from_start;
95+
9596
if (sample_time < first_point_timestamp)
9697
{
97-
const rclcpp::Time t0 = time_before_traj_msg_;
98+
// it changes points only if position and velocity are not exist, but their derivatives
99+
deduce_from_derivatives(
100+
state_before_traj_msg_, first_point_in_msg, state_before_traj_msg_.positions.size(),
101+
(first_point_timestamp - time_before_traj_msg_).seconds());
98102

99103
interpolate_between_points(
100-
t0, state_before_traj_msg_, first_point_timestamp, first_point_in_msg, sample_time,
101-
output_state);
104+
time_before_traj_msg_, state_before_traj_msg_, first_point_timestamp, first_point_in_msg,
105+
sample_time, output_state);
106+
102107
start_segment_itr = begin(); // no segments before the first
103108
end_segment_itr = begin();
104109
return true;
@@ -108,17 +113,20 @@ bool Trajectory::sample(
108113
const auto last_idx = trajectory_msg_->points.size() - 1;
109114
for (size_t i = 0; i < last_idx; ++i)
110115
{
111-
const auto & point = trajectory_msg_->points[i];
112-
const auto & next_point = trajectory_msg_->points[i + 1];
116+
auto & point = trajectory_msg_->points[i];
117+
auto & next_point = trajectory_msg_->points[i + 1];
113118

114-
const rclcpp::Duration t0_offset = point.time_from_start;
115-
const rclcpp::Duration t1_offset = next_point.time_from_start;
116-
const rclcpp::Time t0 = trajectory_start_time_ + t0_offset;
117-
const rclcpp::Time t1 = trajectory_start_time_ + t1_offset;
119+
const rclcpp::Time t0 = trajectory_start_time_ + point.time_from_start;
120+
const rclcpp::Time t1 = trajectory_start_time_ + next_point.time_from_start;
118121

119122
if (sample_time >= t0 && sample_time < t1)
120123
{
124+
// it changes points only if position and velocity are not exist, but their derivatives
125+
deduce_from_derivatives(
126+
point, next_point, state_before_traj_msg_.positions.size(), (t1 - t0).seconds());
127+
121128
interpolate_between_points(t0, point, t1, next_point, sample_time, output_state);
129+
122130
start_segment_itr = begin() + i;
123131
end_segment_itr = begin() + (i + 1);
124132
return true;
@@ -272,6 +280,41 @@ void Trajectory::interpolate_between_points(
272280
}
273281
}
274282

283+
void Trajectory::deduce_from_derivatives(
284+
trajectory_msgs::msg::JointTrajectoryPoint & first_state,
285+
trajectory_msgs::msg::JointTrajectoryPoint & second_state, const size_t dim, const double delta_t)
286+
{
287+
if (second_state.positions.empty())
288+
{
289+
second_state.positions.resize(dim);
290+
if (first_state.velocities.empty())
291+
{
292+
first_state.velocities.resize(dim, 0.0);
293+
}
294+
if (second_state.velocities.empty())
295+
{
296+
second_state.velocities.resize(dim);
297+
if (first_state.accelerations.empty())
298+
{
299+
first_state.accelerations.resize(dim, 0.0);
300+
}
301+
for (size_t i = 0; i < dim; ++i)
302+
{
303+
second_state.velocities[i] =
304+
first_state.velocities[i] +
305+
(first_state.accelerations[i] + second_state.accelerations[i]) * 0.5 * delta_t;
306+
}
307+
}
308+
for (size_t i = 0; i < dim; ++i)
309+
{
310+
// second state velocity should be reached on the end of the segment, so use middle
311+
second_state.positions[i] =
312+
first_state.positions[i] +
313+
(first_state.velocities[i] + second_state.velocities[i]) * 0.5 * delta_t;
314+
}
315+
}
316+
}
317+
275318
TrajectoryPointConstIter Trajectory::begin() const
276319
{
277320
THROW_ON_NULLPTR(trajectory_msg_)

0 commit comments

Comments
 (0)