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 optional per-controller update period #127
Added optional per-controller update period #127
Conversation
Thanks for providing a patch to ros_control! Here are a few comments:
|
I also thought about that, but there are use cases where you want a controller to update at a fixed time interval, independently of how fast the controller manager runs (like in the JointStateController example). Is there a possibility to get the controller manager update rate from the controller's In fact the number of cycles is predictable if you know the manager's update rate and the controller's update rate (given that the
There is no system time querying involved here. This patch only uses the information it gets from the controller_manager as an argument to
What exactly do you mean?
|
Currently the best way to get the update rate is to look at the duration passed into the update call.
Yeah, that's right, so you don't actually get the desired update rate. Even your average update rate will be off. This makes a parameter to skip n cycles more appropriate.
That's awesome, my bad!
Yes, I think I'd prefer this implementation.
There's definitely a risk to break an existing controller, but we could choose a name that's less likely to be already in use. How about something explicit like 'run_every_n_cycles'?
True, but it seems okay to expect a controller to handle more advanced cases itself. Thoughts? |
…riod" This reverts commit 195391a.
…s members to ControllerBase class
…d max_update_frequency parameters to limit the update rate of individual controllers
I updated my pull request according to your suggestions:
|
…f update_every_n_cycle is set
I'm a bit worried about trying to create features for too many usecases, and ending up with a messy interface:
I'd love to hear other people chime in too, but I'd vote for only adding the "run_every_n_cycles" parameter, and not allowing the controller to change this on the fly. I do realize there are valid usecases where you want a more complex behavior, but I think those can be covered inside specific controllers. In any case, @meyerj thanks for your patience in working through this pull request! |
I agree with Wim about keeping things simpler in the base classes and controller manager. The update every n cycles seems to be a reasonable compromise and more complex behaviors could happen in the individual controllers themselves. The intention for ros_control was to support the most common use cases well while leaving flexibility for more complex things to happen. |
Can you give me hint on how to implement the desired behavior (minimum update period/maximum update frequency) with I you prefer, I will remove the |
@meyerj You're right that using the 'run_every_n_cycles' parameter does not give you direct control over the update frequency, as it it tied to the base update rate of the controller manager. But it is a reasonable assumption that you know the base controller rate when configuring the controllers of your robot. Even your controller gains will make an assumption about the controller rate (even when you take the controller period into account inside the controller, the bandwith of your update loop will still affect the gains). With that in mind, the 'run_every_n_cycles' parameter allows you to precisely set the update rates for each controller. The joint_state_publisher controller has been a bit of an exception, where we've allowed people to set an actual frequency, because that controller is not actually controlling anything, and the exact update rate does not matter in that case. But for actual controllers I'd argue that you really do care about the exact update rate, and you don't want to change the trigger rate of the controller manager without updating the parameters of your controllers. What's the usecase you're developing this for? |
I am in the progress of rewriting our controller structure for hector_quadrotor using ros_control and gazebo_ros_control. You can check the current state in the hector_quadrotor_controller package in the ros_control branch of the source repository, if you want. For the real system, there is a whole bunch of controllers involved which realize a cascaded control loop from pose down to motor voltages, where there are different "branches" for different control inputs like horizontal position, height and heading. The inner control loops run on a microcontroller and communicate with the onboard PC via an Ethernet link. The outer loops are currently realized in an Orocos RTT component. For the Gazebo simulation we currently just "bypass" the inner loops by applying torques and forces directly instead of controlling the motor voltages and integrate the Orocos controllers using our gazebo_rtt_plugin. My idea was to rewrite all the control logic using ros_control and instantiate and update the controllers either from an Orocos component in real-time or run them in Gazebo using gazebo_ros_control in simulation, without affecting the current timing too much. My Some of the controllers would only run in Gazebo, as I don't want to touch the microcontroller firmware. Others won't control anything but implement a pre-filter or even parts of the simulation. One example is the current aerodynamics plugin, which takes the commanded motor voltages and simulates the resulting force and torque based on a empirically parameterized motor and propeller model. This "controller" would run as the inner-loop with the highest possible update rate and its output is what is finally applied to the Gazebo model. Although this is not a controller in the common sense, I would like to integrate it in the ros_control structure to avoid publishing and subscribing stuff within the same Gazebo process to overcome plugin boundaries. To come back to the original problem: I am not sure yet if ros_control is the right solution to solve that kind of control problem. Especially there is no out-of-the-box support for cascaded controllers yet and I am not sure how to ensure that the controllers are updated in the right order if I would stop and restart one of them. In any case I must be able to control the update period for each controller individually to simulate the real system as close as possible. I am aware that the update period slightly influences the effective gains, but I did not experience any problems until now if I define all the control parameters as for the time-continuous case and use the given period for the calculation of integrated and differential errors. I will remove the |
I updated the pull request and removed the I also have pushed a rebased branch without the reverts and two stashed commits at https://github.com/meyerj/ros_control/tree/added-per-controller-update-period-rebased. |
ros_control in its current state does not support well multiple update rates. The two existing solutions mentioned previously in the thread can work for specific usecases, but have their drawbacks:
Since these are more workarounds than solutions, my position so far in this matter has been to not touch the core of ros_control, but rather select the best strategy for a specific usecase and implement it inside a controller. Still, if there's demand for the Finally, it's good to know that usecases for multiple controller update rates are starting to appear. |
@@ -222,6 +242,15 @@ bool ControllerManager::loadController(const std::string& name) | |||
return false; | |||
} | |||
|
|||
// Configure update_every_n_cycles parameter | |||
int update_every_n_cycles = 0; | |||
if (c_nh.getParam("update_every_n_cycles", update_every_n_cycles)) |
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.
What happens if the parameter is negative? Could you add a check for that and return false in that case?
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 have no effect as it is checked in ControllerManager::update()
, but I will add an error message in ControllerManager::loadController()
. However, 0 and 1 are valid parameter values with the only difference that update_every_n_cycles = 0
would simply pass the period parameter of ControllerManager::update()
to the controllers while update_every_n_cycles = 1
would recalculate the period based on getLastUpdateTime()
if available. I am not sure if there are cases where these two are not equivalent.
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.
Good point! Also, running every 0 cycles does not make much sense, so I'd make the check here that the parameter has to be >= 1, and set the default value to 1.
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.
I am not sure what you mean. With update_every_n_cycles = 1
as default and minimum value the period would always be recalculated from the time argument of ControllerManager::update(time, period)
ignoring the period argument except for the first invocation after the start. Is this what you want?
I would suggest to additionally change the check in controller_manager.cpp:97 and only throttle and recalculate if spec.update_every_n_cycles > 1
.
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.
The (only) correct way to compute the period over multiple cycles is to add up the periods of the individual cycles. The 'period' values come from the monotonic clock, so they are the only correct way to measure the total period over multiple cycles. The 'time' is the best approximation of the system time, and is not monotonic; it can be adjusted at runtime to stay synchronized with the actual world time.
If you modify the implementation to create a sum of periods (which is the only correct way to implement this), there's no special case needed for update_every_n_cycles == 1. So you just keep adding the period to the sum of periods, until the number of periods equals update_every_n_cycles.
@meyerj Thanks for the comments and the update to the PR. I added a few comments, but after updating those, this PR looks good to go. As for your usecase, ros control indeed does not have support for cascading controllers. I believe right now the controllers get executed in the order that they are loaded, so you'll need to spawn them in sequence to get a reliable execution order. |
…dividual cycles if update_every_n_cycles > 1
Updated with "add up periods" patch. There still is a clean rebased and squashed branch available at https://github.com/meyerj/ros_control/tree/added-per-controller-update-period-rebased, if you prefer. I also made the |
Awesome, this is really getting somewhere! Thanks for all the work! One last comment here, now that we're adding up the durations, can we get rid of last_update_time_ and get the patch nice and compact? |
Hey, I'm picking this one up. I'll write some tests for this feature, which is what's been preventing the merge. |
Closing this in favor of #207. |
This pull request adds an optional per-controller update period to ros_control.
The default update period for a controller is zero and the controller is updated on each update of the controller manager. This should not break existing code.
If the controller sets an individual update period by calling the new
ControllerInterface::setUpdatePeriod()
method, e.g. in its overriddeninit()
method, the controller manager will only invoke update if the desired period has elapsed since the last update and also adapts the period passed to the controller accordingly.I am using ros_control for a cascaded controller where the outer loops should run slower than the inner loops. Of course it would be possible to implement this throttling in the outer controllers itself, but this solution is more general. Another use case is the JointStateController in ros_controllers, where a similar solution is already implemented for the publish rate.
There are also some checks in
ControllerInterface::updateRequest()
that make sure that the controller (hopefully) handles stops and restarts at a later point of time (or a time reset when running in Gazebo) correctly without passing bogus periods to the controller'supdate()
method.