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

[JTC] Move PID to controller plugin #2

Closed
wants to merge 27 commits into from

Conversation

christophfroehlich
Copy link
Owner

@christophfroehlich christophfroehlich commented Oct 14, 2023

Up to now there were only two possibilities of controlling the system with JTC:

  • feedforward only
  • feedback with one single-input-single-output PID per joint.

The aim of this PR is to create an interface for supporting new control strategies, and enable even creating custom controller plugins by users. This is done by plugins of base class TrajectoryControllerBase, loaded by the pluginlib.

Together with the change of #3 it will even be supported to integrate state-space controller where the system output does not have the same size as the system input: See the following swing-up of a cart-pole from this demo, where I implemented a LQR as plugin

swingup_lqr_gh.mp4

Why to change JTC in this way:

  • Of course this could also be done by a new custom ros2_controller, but I'd really like to use the well-tested interfaces of JTC in my projects as well (and get updates to it).
  • In principle, JTC could be enhanced by a chained controller. But: Then there would be no direct way to use the trajectory sent to JTC for calculating the control law, nor does it have the information about the start of the sent trajectory to interpolate the gains to the correct time instance.
  • An alternative would be a JTC base class, with virtual methods for the trajectory controller. The current default JTC is then inheriting this class; users can inherit from the base class with their own methods. Drawback of this would be, that any upstream changes of the JTC base class would force the users to re-build their inherited JTC. This wouldn't be necessary with the proposed plugins.

The API was designed around the following methods:

  • computeControlLawNonRT: called when a new trajectory is received. This may take some time (e.g., calculating the gains for a LQR by solving Riccati equations). This blocks the execution of the new trajectory by JTC until the calculation is finished (see is_ready()). The user has to implement a realtime-buffer to switch the old control law to the new one, once it is started (see start()). In case of any abort (update trajectory with set_hold_position()), there is a computeControlLawRT which needs to finish in the RT loop.
  • updateGainsRT: A fast update within the RT loop, e.g., update of the PID gains from dynamic reconfigured ROS parameters.
  • computeCommands: Evaluate the control law inside the RT loop with earlier calculated/updated gains.

Notes:

  • w.r.t. the change of [JTC] Accepting different number of number of joints and command_joints parameter #3, the map for the gains should be command_joints. But most of the time this parameter is empty and copied over from joints in JTC. Because it is a static read-only variable, it is not possible to override the parameter for the plugin from JTC -> I had to make it reconfigurable from the generate_parameter_library, but added a callback afterwards to prohibit a later change from the user. If anyone knows a better way to solve this, please let me know.
  • The naming of the plugin package, the plugin itself, or the base class is open for discussion.

The behavior of existing configurations should not break with the proposed changes.

@christophfroehlich christophfroehlich changed the title [JTC] controller plugin [JTC] Move PID to controller plugin Oct 23, 2023
Copy link
Collaborator

@saikishor saikishor left a comment

Choose a reason for hiding this comment

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

I'll review rest later. So far it looks spot on

if (use_closed_loop_pid_adapter_)
{
trajectory_msgs::msg::JointTrajectory traj;
traj.points.push_back(state_current_);
Copy link
Collaborator

Choose a reason for hiding this comment

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

@christophfroehlich Maybe also adding the state_desired_ would help

Copy link
Owner Author

Choose a reason for hiding this comment

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

computeGains takes a full trajectory, here it is only initialized with the same points as used for set_hold_position

As mentioned in the description, an update for dynamic reconfigure is missing.

But there is also a call missing for receiving a new trajectory, I'll push an update here.

Copy link
Owner Author

Choose a reason for hiding this comment

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

I pushed a new update

Comment on lines +716 to +715
lock_cmd_joint_names = get_node()->add_pre_set_parameters_callback(
[this](std::vector<rclcpp::Parameter> & parameters)
{
for (auto & parameter : parameters)
{
if (parameter.get_name() == "command_joints")
{
RCLCPP_ERROR(
get_node()->get_logger(),
"The parameter 'command_joints' is read-only. You can't change it.");
parameter = rclcpp::Parameter("command_joints", command_joint_names_);
}
}
});
Copy link
Collaborator

Choose a reason for hiding this comment

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

@christophfroehlich Shouldn't this be set before the set_parameter line?

Copy link
Owner Author

Choose a reason for hiding this comment

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

no because I wouldn't be able to set the parameter then. it should just "lock" the parameter afterwards

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
2 participants