-
Notifications
You must be signed in to change notification settings - Fork 322
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[JTC] Sample at t + dT instead of the beginning of the control cycle #1306
base: master
Are you sure you want to change the base?
Conversation
Since we changed the sampling point we need to adapt the tests.
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #1306 +/- ##
==========================================
+ Coverage 80.34% 80.38% +0.03%
==========================================
Files 105 105
Lines 9384 9402 +18
Branches 826 829 +3
==========================================
+ Hits 7540 7558 +18
- Misses 1570 1571 +1
+ Partials 274 273 -1
Flags with carried forward coverage won't be shown. Click here to find out more.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Look good. I've left some questions
I'll continue reviewing after we discuss this part.
Thanks for your great work @fmauch
joint_trajectory_controller/src/joint_trajectory_controller.cpp
Outdated
Show resolved
Hide resolved
const bool valid_point = traj_external_point_ptr_->sample( | ||
time, interpolation_method_, state_desired_, start_segment_itr, end_segment_itr); | ||
time + update_period_, interpolation_method_, state_desired_, start_segment_itr, | ||
end_segment_itr); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Above you are doing the initial sampling and immediately overriding with this future one. Is this intended?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. The sample call above is - as the comment says - only to establish the actual start time of the trajectory. However, also in the first control cycle when we started the trajectory we want to sample at
This should be probably refactored in the trajectory representation that the start time would not implicitly be specified by the first sample, but I thought this might not be the right PR to address that...
@@ -279,7 +284,7 @@ controller_interface::return_type JointTrajectoryController::update( | |||
tmp_command_[i] = (state_desired_.velocities[i] * ff_velocity_scale_[i]) + | |||
pids_[i]->computeCommand( | |||
state_error_.positions[i], state_error_.velocities[i], | |||
(uint64_t)period.nanoseconds()); | |||
(uint64_t)update_period_.nanoseconds()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shouldn't this use the period itself?
I think the period
is more correct than the update_period_
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would usually not make that big of a difference, but isn't the goal here, as well, to generate a command to reach state_desired
at the end of the current control cycle (which will be update_period_
in the future).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, I agree with Sai here: This is not related to the trajectory sampling but only for calculating the feedback control law (in fact, only for integrating the error for the integral part of the command).
joint_trajectory_controller/src/joint_trajectory_controller.cpp
Outdated
Show resolved
Hide resolved
@@ -644,7 +646,7 @@ TEST_P(TrajectoryControllerTestParameterized, position_error_not_angle_wraparoun | |||
size_t n_joints = joint_names_.size(); | |||
|
|||
// send msg | |||
constexpr auto FIRST_POINT_TIME = std::chrono::milliseconds(250); | |||
constexpr auto FIRST_POINT_TIME = std::chrono::milliseconds(300); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the control period is 100 milliseconds, shouldn't it be 350?
Am i missing something here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why? Would it be desired to set a target time between two control cycles?
With a control period of 100 ms we would call update
at
Co-authored-by: Sai Kishor Kothakota <[email protected]>
traj_controller_->wait_for_trajectory(executor); | ||
auto end_time = updateControllerAsync( | ||
rclcpp::Duration(delay), rclcpp::Time(0, 0, RCL_STEADY_TIME), | ||
rclcpp::Duration::from_seconds(1.0 / traj_controller_->get_update_rate())); | ||
|
||
if (traj_controller_->has_position_command_interface()) | ||
{ | ||
// Check that we reached end of points_old[0] trajectory | ||
auto state_feedback = traj_controller_->get_state_feedback(); | ||
for (size_t i = 0; i < expected_actual.positions.size(); ++i) | ||
{ | ||
EXPECT_NEAR(expected_actual.positions[i], state_feedback.positions[i], 0.01); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason that you don't call waitAndCompareState() here? why is now traj_controller_->has_position_command_interface()
necessary, but wasn't before?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you look at the implementation of waitAndCompareState
, you'll see that it is almost equivalent:
traj_controller_->wait_for_trajectory(executor);
auto end_time = updateControllerAsync(controller_wait_time, start_time);
// get states from class variables
auto state_feedback = traj_controller_->get_state_feedback();
auto state_reference = traj_controller_->get_state_reference();
for (size_t i = 0; i < expected_actual.positions.size(); ++i)
{
SCOPED_TRACE("Joint " + std::to_string(i));
// TODO(anyone): add checking for velocities and accelerations
if (traj_controller_->has_position_command_interface())
{
EXPECT_NEAR(expected_actual.positions[i], state_feedback.positions[i], allowed_delta);
}
}
for (size_t i = 0; i < expected_desired.positions.size(); ++i)
{
SCOPED_TRACE("Joint " + std::to_string(i));
// TODO(anyone): add checking for velocities and accelerations
if (traj_controller_->has_position_command_interface())
{
EXPECT_NEAR(expected_desired.positions[i], state_reference.positions[i], allowed_delta);
}
}
- I preferred giving a specific update rate to the
updateControllerAsync
function that matches the actual update rate. - The
waitAndCompareState
function only checks positions, hence I also only checked positions. So, it wasn't necessary before, because it was insidewaitAndCompareState
. - More importantly,
waitAndCompareState
checks for the actual position and the generated position command. However, due to spline interpolation we don't necessarily know the generated command. Assumingexpected_desired = expected_actual;
is simply wrong as long as the robot is in motion (which it is, continuing to the second waypoint). It did work, more or less by accident, since the robot is initialized at{1.2, 2.1, 3.1}
and moves to{2.0, 3.0, 4.0}
in the first segment. Hence, the trajectory "arrives" at the setpoint quite slowly. However, in the next segment (which has the same time), it is supposed to go to{4.0, 5.0, 6.0}
, so it has to move much faster in that segment. Before my change, sampling was basically moved back one cycle, so the desired state was 2.0 for joint1 which matches the desired state and the actual state was something right before that, which was in the range of the 0.1 tolerance (which is probably the reason why it was so large). However, after the changes from this PR, the actual state will be at 2.0 at this time and the desired will be on the way towards 4.0 already. (In fact, it will be at 2.3599999999999999). If we move sampling back by one control cycle torclcpp::Duration(delay) - rclcpp::Duration::from_seconds(0.1)
the desired state matches 2.0 exactly, while the actual state is 1.982, which falls into the 0.1 range. Since the comment to that check saysCheck that we reached end of points_old[0]trajectory
I thought it would be better to check exactly that with a small tolerance instead of having to increase the tolerance to actually not fail a check that doesn't only check what we want to know at that point. I hope, that paragraph makes sense...
This isn't used anymore and more or less confusing, as actual will not be desired as long as the robot is moving, which it is here.
The changes from ros-controls/ros2_control#1788 require revisiting the tests from this. I expect, that tests on the master branch will fail, as well, however, since I had to set correct rates all over the place within this PR. Well, maybe not. I'll check this one, though. Edit: Things are back to normal now. |
Some tests require an update rate of 100 which isn't possible if the default one is at 10.
As mentioned in #1191 (comment) and discussed in #697 the JTC currently samples the trajectory at the time given to the
update(time, period)
method. However, this actually is the beginning of the control cycle and it would make more sense to sample the setpoint for the end of the control cycle, resulting intime + controller_update_period
. This PR implements that.I obviously had to update a couple of tests on the way, since with changing the sampling point, the resulting command can be significantly different.