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

Added dynamic reconfigure for PID gains #1

Merged
merged 13 commits into from
Jul 29, 2013

Conversation

davetcoleman
Copy link
Member

You can now set the P, I, D and i_clam_min/max using dynamic reconfigure

@davetcoleman
Copy link
Member Author

I added this so that Gazebo users can easily tune their robot controllers to work in simulation
@jbohren @adolfo-rt

@davetcoleman
Copy link
Member Author

@adolfo-rt can you review this PR and its real-time controller implications?

ROS_ERROR("No p gain specified for pid. Namespace: %s", n.getNamespace().c_str());
return false;
}
n.param("i", i_gain_, 0.0);
n.param("d", d_gain_, 0.0);
if (!n.getParam("i", i_gain_))
Copy link
Member

Choose a reason for hiding this comment

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

init should not fail when the derivative or integral gains are not specified. The previous behavior of defaulting them to zero without raising an error should be preserved. We should allow specifying P, PD or PI controllers without requiring the explicit setting of the unused gains. The proportional gain is error-checked because it is unlikely you'll find a controller without it.

Copy link
Member Author

Choose a reason for hiding this comment

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

Ah, that makes a lot of sense. I thought it had just been coding laziness!

@davetcoleman
Copy link
Member Author

@adolfo-rt i have made your suggested fixes, can you double check the mutex usage here: https://github.com/ros-controls/control_toolbox/pull/1/files#L4R168 please?

Thanks!


getGains(config.p_gain, config.i_gain, config.d_gain, config.i_clamp_max, config.i_clamp_min);

// Set starting values, using a shared mutex with dynamic reconfig
Copy link
Member Author

Choose a reason for hiding this comment

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

Does this look right?

Copy link
Member

Choose a reason for hiding this comment

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

I think so, yes.

@davetcoleman
Copy link
Member Author

Who has commit access to control_toolbox? I seem to only have access to ros_controls and ros_controllers

${Boost_INCLUDE_DIR}
)

find_package(Boost REQUIRED COMPONENTS system thread)

Choose a reason for hiding this comment

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

Doing find_package(Boost ...) after setting the include dirs to include boost seems like a bug.

Copy link
Member Author

Choose a reason for hiding this comment

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

I've moved it above the include_directories line.

@ahendrix
Copy link

I think the assignment operator needs an update; probably just an added call to updateDynamicReconfig().

@ahendrix
Copy link

I'm worried about locking around the p_gain_, i_gain_, d_gain_, i_max_ and i_min_ terms.

Without locking, it's possible that the dynamic_reconfigure callback will update a few of the terms and get preempted while the realtime part of the controller runs, at which point it will see an inconsistent set of gains.

With locking around these parameters, it's possible that the dynamic_reconfigure callback could block execution of the realtime part of the controller, which could cause the entire realtime loop to miss its deadline.

I know some parts of this code operate in realtime and some parts do not; is there a clear delineation of which methods run as part of the realtime controller and which do not?

@davetcoleman
Copy link
Member Author

From what I can tell, the only risk of breaking the realtime run loop is if you:

  • Call Pid.setGains() within your real time controller. I don't see why someone would do this but we can document proper warnings against doing so
  • Use the Pid X = Pid Y assignment operator (which also triggers dynamic reconfigure). You really wouldn't want to do this in a realtime loop.

@adolfo-rt
Copy link
Member

@ahendrix, @davetcoleman
A solution would be to use the RealtimeBuffer from realtime_tools. The class is undocumented :( but should be easy to use.

The recursive lock passed to dynamic_reconfigure would not be used inside the PID class. Instead, when new values arrive via a reconfigure update, one would safely make them effective by doing a writeFromNonRT(), and reading them would be done with readFromRT().

Lastly, I would advise to put the gains inside of a struct, so that only one RealtimeBuffer insance is needed instead of one for every gain/parameter.

@davetcoleman
Copy link
Member Author

I've seen the RealtimeBuffer used in ros_control[lers] in lots of places, and I'll implement it here, but is this really needed you think?

@adolfo-rt
Copy link
Member

I've seen the RealtimeBuffer used in ros_control[lers] in lots of places, and I'll implement it here, but is this really needed you think?

If the alternative is using the recursive_mutex shared with dynamic_reconfigure, then yes; otherwise there is a risk of a priority inversion. In the end what you want is to read data using a fast, constant-time operation (ie. deterministic). The RealtimeBuffer provides that.

Notice that the RealtimeBuffer uses internally a lock, but it only attempts a try_lock when reading from realtime, and if that fails it returns a pointer to the last (cached) valid data. For completeness, Orocos offers alternative implementations of data structures not subject to priority inversions, summarized here. I don't think it's worth the additional dependency when you can get away with realtime_tools.

@ahendrix
Copy link

+1; RealtimeBuffer sounds like the right solution.

@davetcoleman see my reasoning above for why I think this is required. Realtime constraints don't seem to be a big problem in Gazebo, but they're a very real problem on a real robot where we're running the controllers at 1kHz, and a delay of more than a few hundred microseconds could halt the system.

@davetcoleman
Copy link
Member Author

I've added the realtime buffer... it required a lot of changes and in order for me to thoroughly make sure I implemented it right I re-ordered some of the functions such that they are grouped by function (initialization, etc) and their ordering matches the header file. My apologies for the diff.

I'd like to do some more runtime testing before merging this.

@ahendrix
Copy link

I think this looks good overall. More testing is better.

@ahendrix
Copy link

Is it still possible to set the gains through the old parameters?

@adolfo-rt
Copy link
Member

Thanks for taking on this Dave. It's a great usability improvement for the PID class.

@davetcoleman
Copy link
Member Author

Thoughts on putting this inside of Pid so that it's control_toolbox::Pid::Gains instead of control_toolbox::Gains ?

Done

Since you're no longer trying to return multiple things, I think it would make more sense for this to be Gains getGains();

Done

Is it still possible to set the gains through the old parameters?

Yes, I haven't changed any of the old api


There is one issue though... the ability to do
Pid pid2 = pid1;

In the old implementation (without a realtime buffer) it had an opertor= overload that did some custom stuff:

  Pid &operator =(const Pid& p)
  {
    if (this == &p)
      return *this;

    p_gain_ = p.p_gain_;
    i_gain_ = p.i_gain_;
    d_gain_ = p.d_gain_;
    i_max_ = p.i_max_;
    i_min_ = p.i_min_;

    p_error_last_ = p_error_ = i_term_ = d_error_ = cmd_ = 0.0;
    return *this;
  }

Now with the real time buffer, we can't do

    p_gain_ = p.p_gain_;
    i_gain_ = p.i_gain_;
    d_gain_ = p.d_gain_;
    i_max_ = p.i_max_;
    i_min_ = p.i_min_;

But we also can't do any kind of accessing or copying of the gains_buffer_ because the gains buffer has a non-const readFromRT() and assignment functions pass in a const copy of the original element.

The only solution I can think of is adding a new function to realtime_buffer.h like this:

T* readFromRTConst() const
  {                                     
    return realtime_data_; 
  }  

But I'm not entirely sure if this will return the latest data/if its a good addition. I'll add a pull request do discuss this idea on the realtime_tools repo...

@davetcoleman
Copy link
Member Author

I think I have resolved the overloaded assignment operator issue as well as removed the need for adding a new readFromRT() function in the realtime_tools package.

It should be pretty stable now.

*/
void getCurrentPIDErrors(double *pe, double *ie, double *de);
Gains getGainsConst() const;

Choose a reason for hiding this comment

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

getGains() should probably be const, which would make this redundant.

having const and non-const methods that do exactly the same thing is redundant and confusing.

Copy link
Member Author

Choose a reason for hiding this comment

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

As of today's fixes, getGainsConst() calls a different function in the runtime_buffer class - it calls a blocking readFromRTConst(). I think providing this optional functionality is beneficial for certain applications.

@davetcoleman
Copy link
Member Author

I upgraded the PID tests to use the new computeCommand() function and I added a bunch of tests for getting/setting PID gains as well as coping/assigning the Pid class.

@davetcoleman
Copy link
Member Author

@ahendrix can we merge and release this too? thanks!


Pid::Gains Pid::getGains() const
{
return *gains_buffer_.readFromNonRT();

Choose a reason for hiding this comment

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

Having different versions of getGains that differ only in const-ness, one of which is realtime-safe and one of which is not seems like a good way to introduce bugs.

Is the non-const version useful for anything?

I suggest we either eliminate the non-const version or rename them to indicate realtime safety or lack thereof; perhaps getGainsRT and getGainsNonRT.

Copy link
Member Author

Choose a reason for hiding this comment

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

I was using it for the printValues() helper function, but I have removed the const getGains() function.


/*!
* \brief Get PID gains for the controller.
* \return gains A struct of the PID gain values

Choose a reason for hiding this comment

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

Having const and non-const methods with the same name, one of which is realtime-safe and the other of which is not sounds like a recipe for bugs.

I suggest you rename them to 'getGainsNonRT' and 'getGainsRT' or something similar that reflects the realtime-safety of each method, or just remove one of them.

Copy link
Member Author

Choose a reason for hiding this comment

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

Did you mean to post this comment again? I fixed this issue last Friday. There are no const getters anymore.

ahendrix pushed a commit that referenced this pull request Jul 29, 2013
Added dynamic reconfigure for PID gains
@ahendrix ahendrix merged commit 81f02fd into ros-controls:hydro-devel Jul 29, 2013
@davetcoleman
Copy link
Member Author

Release to build farm?

@ahendrix
Copy link

Doesn't build on my machine; copy constructors are broken:

In file included from /usr/include/c++/4.6/x86_64-linux-gnu/./bits/c++allocator.h:34:0,
                 from /usr/include/c++/4.6/bits/allocator.h:48,
                 from /usr/include/c++/4.6/vector:62,
                 from /wg/stor2a/ahendrix/hydro/src/pr2_controllers/robot_mechanism_controllers/include/robot_mechanism_controllers/cartesian_pose_controller.h:72,
                 from /wg/stor2a/ahendrix/hydro/src/pr2_controllers/robot_mechanism_controllers/src/cartesian_pose_controller.cpp:36:
/usr/include/c++/4.6/ext/new_allocator.h: In member function ‘void __gnu_cxx::new_allocator<_Tp>::construct(__gnu_cxx::new_allocator<_Tp>::pointer, const _Tp&) [with _Tp = control_toolbox::Pid, __gnu_cxx::new_allocator<_Tp>::pointer = control_toolbox::Pid*]’:
/usr/include/c++/4.6/bits/stl_vector.h:830:6:   instantiated from ‘void std::vector<_Tp, _Alloc>::push_back(const value_type&) [with _Tp = control_toolbox::Pid, _Alloc = std::allocator<control_toolbox::Pid>, std::vector<_Tp, _Alloc>::value_type = control_toolbox::Pid]’
/wg/stor2a/ahendrix/hydro/src/pr2_controllers/robot_mechanism_controllers/src/cartesian_pose_controller.cpp:103:45:   instantiated from here
/usr/include/c++/4.6/ext/new_allocator.h:108:9: error: no matching function for call to ‘control_toolbox::Pid::Pid(const control_toolbox::Pid&)’
/usr/include/c++/4.6/ext/new_allocator.h:108:9: note: candidates are:
/wg/stor2a/ahendrix/hydro/src/control_toolbox/include/control_toolbox/pid.h:156:3: note: control_toolbox::Pid::Pid(control_toolbox::Pid&)

The main problem here is that the copy constructor for control_toolbox::Pid does not take a const argument. I'll work on it...

@ahendrix
Copy link

Fixed by making the copy constructor parameter const.

Released control_toolbox 1.10.2 to Hydro: ros/rosdistro#1578

@davetcoleman
Copy link
Member Author

Thanks!

bmagyar added a commit that referenced this pull request Jul 1, 2020
* Update gh actions versions

* Cleanup doc and destructor
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.

None yet

3 participants