Skip to content
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

[Servo] Correct for drift by using previously commanded state instead of current state #2349

Closed
wants to merge 7 commits into from

Conversation

sea-bass
Copy link
Contributor

@sea-bass sea-bass commented Sep 8, 2023

This PR implements the suggestion in #1857 (comment) to try correct drift for robots without great joint tracking control (like the MoveIt Studio Gazebo simulation).

This adds an apply_anti_drift parameter to MoveIt Servo, which when enabled will use the previously commanded joint state instead of the current state to compute joint deltas.

@sea-bass
Copy link
Contributor Author

sea-bass commented Sep 8, 2023

@ibrahiminfinite

@ibrahiminfinite
Copy link
Contributor

The std::optional passing approach seems a bit complicated.

How about something like this :

1 . Move last_commanded_state_ from ServoNode to Servo
2 . Compute error here and apply it to target_joint_positions.

 
// Compute error from last commanded state
joint_position_error = last_commanded_positions_  - current_joint_positions
// Compute the next joint positions based on the joint position deltas
target_joint_positions = current_joint_positions + joint_position_delta + joint_position_error;

or

 
// Compute error from last commanded state
joint_position_error = last_commanded_positions_  - current_joint_positions
// Compute the next joint positions based on the joint position deltas
target_joint_positions = current_joint_positions + joint_position_delta;

if(servo_params_.apply_anti_drift)
    target_joint_positions += joint_position_error;

last_commanded_positions_ will be an Eigen::Map of last_commanded_state_.positions
This way none of the public APIs have to change.

The smoothHalt() method can then just use the last_command_state_ without needing to pass it in, since it will already be available as a member variable in Servo.

@sea-bass
Copy link
Contributor Author

sea-bass commented Sep 8, 2023

I actually started doing it exactly that way you suggested... but then realized that we actually want to be computing the joint delta from the last commanded state instead of from the current state like in this line. This affects results when responding to twist or pose commands.

That said, the suggestion to move away from optionals and stick the last commanded state in Servo instead of ServoNode is valid. That lets the optional argument to getNextJointState() simply be a boolean. I can look at that when I find some more time.

@codecov
Copy link

codecov bot commented Sep 8, 2023

Codecov Report

Patch coverage: 64.45% and project coverage change: -0.03% ⚠️

Comparison is base (84421ea) 50.74% compared to head (567469c) 50.71%.

❗ Current head 567469c differs from pull request most recent head 2078dd3. Consider uploading reports for the commit 2078dd3 to get more accurate results

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2349      +/-   ##
==========================================
- Coverage   50.74%   50.71%   -0.03%     
==========================================
  Files         386      386              
  Lines       31958    31968      +10     
==========================================
- Hits        16215    16210       -5     
- Misses      15743    15758      +15     
Files Changed Coverage Δ
moveit_ros/moveit_servo/src/servo.cpp 63.14% <61.54%> (-0.73%) ⬇️
moveit_ros/moveit_servo/src/servo_node.cpp 77.52% <83.34%> (-3.43%) ⬇️

... and 2 files with indirect coverage changes

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@@ -126,10 +127,21 @@ class Servo

/**
* \brief Smoothly halt at a commanded state when command goes stale.
* @param The last commanded joint states.
* @param halt_state_maybe A std::optional containing the last commanded joint states, if available.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* @param halt_state_maybe A std::optional containing the last commanded joint states, if available.

Outdated comment

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like the type of thing that should be a free function, eh? Transparent inputs and outputs

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is a public API of Servo which is used by ServoNode, not sure if making it a free function would make sense contextually even though it can easily be a free function.

@@ -200,6 +212,8 @@ class Servo
std::shared_ptr<const servo::ParamListener> servo_param_listener_;
planning_scene_monitor::PlanningSceneMonitorPtr planning_scene_monitor_;

std::optional<KinematicState> last_commanded_state_;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
std::optional<KinematicState> last_commanded_state_;
KinematicState last_commanded_state_;

I think this should not be optional, since last_commanded_state_ should technically always have a value.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'm ok with the optional. Maybe it wouldn't have a value if the program were just started or somebody just called resetLastCommandedState()

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it wouldn't have a value if the program were just started or somebody just called resetLastCommandedState()

Would it make sense for resetLastCommandedState() to reset it to the current state rather than a std::nullopt.
We could then call this same function in the constructor to initialize the last_commanded_state_.

@ibrahiminfinite
Copy link
Contributor

I am not sure about computing the required joint delta by setting robot_state positions to last_commanded_state_.
This would mean we are computing the delta assuming that the robot actually reached the last_commanded_state_.

What I think should happen is:
In case of a drift, the robot is currently at positions q_current (under/overshoot), but should have been at q_target.
So when we compute the new joint delta, we want the error q_target - q_current to be accounted for.
If we use the last commanded positions q_target, the delta would be smaller/larger than what it should be, depending on if the drift was an undershoot or overshoot.

@@ -74,6 +74,7 @@ class Servo
/**
* \brief Computes the joint state required to follow the given command.
* @param command The command to follow, std::variant type, can handle JointJog, Twist and Pose.
* is used to calculate an error between the current state and the previous commanded state for anti-drift correction.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Continuing the formatting nitpick theme ;)

Suggested change
* is used to calculate an error between the current state and the previous commanded state for anti-drift correction.
* Is used to calculate an error between the current state and the previous commanded state for anti-drift correction.

@@ -126,10 +127,21 @@ class Servo

/**
* \brief Smoothly halt at a commanded state when command goes stale.
* @param The last commanded joint states.
* @param halt_state_maybe A std::optional containing the last commanded joint states, if available.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like the type of thing that should be a free function, eh? Transparent inputs and outputs

@@ -126,10 +127,21 @@ class Servo

/**
* \brief Smoothly halt at a commanded state when command goes stale.
* @param The last commanded joint states.
* @param halt_state_maybe A std::optional containing the last commanded joint states, if available.
* @return The next state stepping towards the required halting state.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does the bool component of the returned value mean?

@@ -200,6 +212,8 @@ class Servo
std::shared_ptr<const servo::ParamListener> servo_param_listener_;
planning_scene_monitor::PlanningSceneMonitorPtr planning_scene_monitor_;

std::optional<KinematicState> last_commanded_state_;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'm ok with the optional. Maybe it wouldn't have a value if the program were just started or somebody just called resetLastCommandedState()

auto initial_state = current_state;
if (servo_params_.apply_anti_drift && last_commanded_state_.has_value())
{
robot_state->setJointGroupPositions(joint_model_group, last_commanded_state_.value().positions);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we know last_commanded_state_ has a value

Suggested change
robot_state->setJointGroupPositions(joint_model_group, last_commanded_state_.value().positions);
robot_state->setJointGroupPositions(joint_model_group, *last_commanded_state_.positions);

@@ -423,7 +432,7 @@ KinematicState Servo::getNextJointState(const ServoInput& command)
joint_position_delta *= collision_velocity_scale_;

// Compute the next joint positions based on the joint position deltas
target_joint_positions = current_joint_positions + joint_position_delta;
target_joint_positions = initial_joint_positions + joint_position_delta;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe i missed it but i don't see that current_joint_positions was modified at all between L410 and here. So why not use current_joint_positions and don't bother creating initial_joint_positions?

}
auto& halt_state = last_commanded_state_.value();
const size_t num_joints = current_state.joint_names.size();
for (size_t i = 0; i < num_joints; i++)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit

Suggested change
for (size_t i = 0; i < num_joints; i++)
for (size_t i = 0; i < num_joints; ++i)

{
smoother_->reset(current_state.positions);
smoother_->doSmoothing(halt_state.positions);
return std::make_pair(stopped, current_state);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

stopped appears to be false here, I think it should be true

moveit_ros/moveit_servo/src/servo.cpp Show resolved Hide resolved
@sea-bass
Copy link
Contributor Author

Guys, I am not sure this ended up helping at all, so I may close this PR. Still need to do some more testing.

@sea-bass sea-bass marked this pull request as draft September 10, 2023 12:01
@sea-bass sea-bass closed this Sep 10, 2023
@sea-bass sea-bass deleted the servo-anti-drift branch July 31, 2024 00:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants