Skip to content

Action goal checker callback (wip)#2118

Closed
gramss wants to merge 2 commits intoros-navigation:mainfrom
gramss:action-goal-checker-wip
Closed

Action goal checker callback (wip)#2118
gramss wants to merge 2 commits intoros-navigation:mainfrom
gramss:action-goal-checker-wip

Conversation

@gramss
Copy link
Copy Markdown
Contributor

@gramss gramss commented Dec 9, 2020

Adding a new callback function into SimpleActionServer.
This callback is optional, so no ABI breaking changes here.
BT_Navigator for instance uses this to check if a BT.XML is accessible and therefor can REJECT the goal even before it further processes it.

This callback can be used by any other Action server and is template based (Action independent).
Could also come in handy for #1971 -> check if there is currently another goal happening that is using another current_bt_ than the one from the new goal. Therefor REJECTING the request and not PREEMTING the current BT (or some other behavior that has yet to be decided from/in #1971 )

This callback is called during the initial goal request (also see the action design docs):

design-action-call

So instead of the ACCEPT here, the goal checker could initiate a REJECT inside this Service-Request. This could save some time. But bigger checks like (Can I get a path to this goal) probably would exceed the acceptable lifespan of a service request?


Basic Info

Info Please fill out this column
Ticket(s) this addresses (add tickets here #1971 #2010 )
Primary OS tested on (Ubuntu)
Robotic platform tested on headless gazebo simulation in docker with system_tests
DDS probably Fast-RTPS (what lives in the main.release-Docker?)

Description of contribution in a few bullet points

  • I tried to add this feature according to the coding-style used in this scope (taking into account different typedefs and namespaces)

Description of documentation updates required from your changes

  • Added inline code description
  • Add tutorial on how to use it (based on this PR example of usage)

Future work that may be required in bullet points

  • Add proper Tests for this, so it does not compromise the system integrity

if (result):
result = test_RobotGetsBadBehaviorTree(robot_tester)

time.sleep(5)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I had very strange errors here.. I think there exist already a PR/issue somewhere that's about complaining that we cannot "stress test" the bt_navigator with new goals or something.. Might also be a DDS specific error.. Actually seeing quite a few here with the new Callback (but very sporadic.. Once existing, 3 times not. This in general was reported also by others that local tests might fail sometimes and pass on CI..)

typename nodeT::SharedPtr node,
const std::string & action_name,
ExecuteCallback execute_callback,
GoalCheckerCallback goal_checker_callback = nullptr,
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

as an optional argument, all the existing action servers (Planner, ComposePath, BT_Navigator, ...) do not need to bother about it much. But if interested it can be added. I hope this is performant enough?
Nullptr seems appropriate for an empty std::function<>..?

@codecov
Copy link
Copy Markdown

codecov bot commented Dec 9, 2020

Codecov Report

Merging #2118 (8bd158a) into main (199ef58) will decrease coverage by 0.13%.
The diff coverage is 25.00%.

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #2118      +/-   ##
==========================================
- Coverage   83.02%   82.89%   -0.14%     
==========================================
  Files         245      245              
  Lines       11944    11950       +6     
==========================================
- Hits         9917     9906      -11     
- Misses       2027     2044      +17     
Flag Coverage Δ
project 82.89% <25.00%> (-0.14%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted Files Coverage Δ
nav2_controller/src/nav2_controller.cpp 89.08% <0.00%> (-1.19%) ⬇️
nav2_planner/src/planner_server.cpp 89.14% <0.00%> (-1.48%) ⬇️
nav2_recoveries/src/recovery_server.cpp 95.00% <50.00%> (-1.16%) ⬇️
nav2_navfn_planner/src/navfn_planner.cpp 87.42% <0.00%> (-5.99%) ⬇️
nav2_util/src/dump_params.cpp 91.75% <0.00%> (-1.65%) ⬇️
nav2_amcl/src/amcl_node.cpp 85.15% <0.00%> (+0.30%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 199ef58...7bc04a6. Read the comment docs.

@SteveMacenski
Copy link
Copy Markdown
Member

Please offer a clear explanation of what this feature is and why it's necessary, I don't see that and I can't easily decipher it from reading the PR code.

Also goal checker has meaning elsewhere in the stack, you may not use that name. That's also part of the confusion.

Copy link
Copy Markdown
Member

@SteveMacenski SteveMacenski left a comment

Choose a reason for hiding this comment

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

It seems like you want to expose more of the raw action server from the simple action server - and for what you would like to do, there is already an option to handle preemption requests and accept / deny them. I think maybe this didn't come to mind since all of our uses of the simple action server simply accepts blindly all preemption requests, but in the is_preempt_requested blocks, we can chose to accept or not each request.

I think what you're trying to implement can just go inside of the if is_preempt_requested blocks to check if we want to accept it without any action server changes. The action server changes you propose are valid, but also exposes so much of the normal action server that its probably good to just use the normal action server in that situation.

There's definitely nothing wrong with what you propose, but I wonder thematically if that is appropriate for the "simple" action server to do, especially when there's a way to accomplish the same task using the current server structure we have in the BT/control/planner/recovery action server implementations.

}

// Only ckeck the goal if a callback was bind
if (goal_checker_callback_ != nullptr) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

https://github.com/ros-planning/navigation2/blob/main/nav2_bt_navigator/src/bt_navigator.cpp#L283 our servers have the ability already to check if a new goal is been requested to handle. This seems like an analog that breaks the "simple" of the simple action server. One of the goals of the simple action server is to remove all of these callback functions and let us talk to an action server object.

This re-exposes one of the action server callbacks, at that point, I'd think it makes sense to just use the normal action server in this situation. I recognize that the existing implementation relies on polling for updates, so there would have to be a good & specific reason to also expose this (or remove the existing polling feature)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

But with the catch that the simple server can stay simple..?
This callback function is not a hard requirement.

But yes. It re-exposes this one feature. I find it valuable to reexpose this and place "checkers" like that in the designed position.

This would keep your robot to stop for a goal that does not meet very simple requirements. Because when you accept it you have to deal with preemtion and try to stop it at a far later point. Everything is doable, but I just like to have such checkers at the most valuable position.

Stuff about preemtion should probably live in a extra preemtion_checker space. But I believe there exists a valid space for such action_goal_checkers

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think we're covering all the comment topics in the main-level discussion, let's chat there about these "big" questions and then readdress the specific comments once we have some agreement in general design

}

bool
BtNavigator::goalCheckerCallback(std::shared_ptr<const typename Action::Goal> goal)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It seems like this same logic could just be placed in the preemption checks https://github.com/ros-planning/navigation2/blob/7bc04a6c5f0f081292d04339ec1fcbd6d61bf669/nav2_bt_navigator/src/bt_navigator.cpp#L294 that we just blindly accept right now. We could just add a check there and modulate the return code appropriately.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

yes. But this implementation is a little bit higher in the callstack. Preemption checks come right after the action-goal is accepted

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

But wouldn't adding

if is_preempt_requested:
  // get and check goal's validity per specific server needs
  if OK:
    accept
  else:
    reject

Accomplish the same thing without modifying simple action server and enable the behavior you were looking to add to the BT navigator?

Copy link
Copy Markdown
Contributor Author

@gramss gramss Dec 10, 2020

Choose a reason for hiding this comment

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

nope.

I want to check if the xml is loadable before accepting the action_goal.

is_preempt_requested is called by the current active / running "goal", actually by the BT. So far more down the "callstack":

https://github.com/ros-planning/navigation2/blob/7bc04a6c5f0f081292d04339ec1fcbd6d61bf669/nav2_bt_navigator/src/bt_navigator.cpp#L320

So if the robot is just in IDLE and waiting for a new goal -> I would not be able to "pre" check if the xml is loadable with the is_preempt_requested. And this started questioning me if it is necessary to check this on multiple occasions:

  1. in the load_bt() -> load the new bt
  2. in is_preempt_requested (otherwise we would stop a current goal for a bad one)
  3. Potentially some other places

This (PR) however is the point of minimal overlap and should be the exact place of this new (re-introduced) callback.

Also: You cannot REJECT it any more at this place as you accepted it per Action-server-design. So you would cancel it. But I think that would not be the worst, as I just got my head around that there could be multiple accepted goals (current + pending or potential more if wanted). So Canceling in this case would not cancel all goals (including the current, which I was mainly afraid of)

Still: I think it would be better if goals are REJECTED and not Cancelled. This would be a better implementation.


(Probably OT) Also:
https://github.com/ros-planning/navigation2/blob/199ef58f04bce9da4fcf8800630d171665349015/nav2_util/include/nav2_util/simple_action_server.hpp#L230

This is the reason why with PREEMTION no new xmls can be loaded.. As it just updates the goal variable and the BT keeps running.
(No new work() gets triggered/launched)

@gramss
Copy link
Copy Markdown
Contributor Author

gramss commented Dec 10, 2020

Ah snap. Yes.. goal_checker in nav2 context is the threshold if a robot has reached its goal[-position]. Probably should name it action-goal-checker

So my proposition:

Currently, all SimpleActionServer implementations gladly accept any action-goal, except if the server is currently not running. (In the flow-graph this is the second step that we could alter to stop jumping into the rest of the action)
Code here: (the goal is not even touched..)
https://github.com/ros-planning/navigation2/blob/199ef58f04bce9da4fcf8800630d171665349015/nav2_util/include/nav2_util/simple_action_server.hpp#L80

So I thought it might be interesting to potential increase performance in some instances where the action-goal can be rejected based on very simple pre-checks.

Look at the example Action Server implementation:

https://github.com/ros2/examples/blob/2366967a31bbbb9a7b56eb4f71bb32b5c3557e8e/rclcpp/actions/minimal_action_server/member_functions.cpp#L56

Here you can see that it is intended by rclp_action to have such basic tests. The problem we have now with our SimpleActionClient is that is beeing reused by multiple packages.

Therefore, adding those small tests is A not nicely doable directly in our SimpleActionServer due to different action calls and B not useful to check for things that are not intended for the current action_server implementation.
That's why I wanted to add a callback function where everybody can happily integrated there minimal action-goal-checkers, like here:

https://github.com/ros-planning/navigation2/blob/7bc04a6c5f0f081292d04339ec1fcbd6d61bf669/nav2_bt_navigator/src/bt_navigator.cpp#L344

Or just ignore it, as it is optional.
I do not think that directly implementing an action_server from ground would make sense, as our SimpleActionServer holds some interesting logics that I do neither want to replicate nor alter, except adding this optional callback.

Maybe also see my 2 comments on your comments for further details on your questions

@gramss
Copy link
Copy Markdown
Contributor Author

gramss commented Dec 10, 2020

As I was writing this answer, you seemed to wrote your comment. I somehow saw your comment on those files but not the overlaying comment. Sorry for ignoring that.

I believe there are 2 problems which deserve 2 solutions.
I take back my proposition that my new action_goal_checker should have anything to do with preemtion checks.
You are totally right. We have the action_server_->is_preemted() check for that and should use it.

But I still believe that it makes sense in some cases like this if very very basic conditions apply to your specific goal. Can the file be opened from the server, is a goal_field out of some very basic limit. Stuff like that.
And anything regarding preemtion should happen in the already installed methods.

It is really cleverly solved in the bt_navigator. Thanks for pointing it out! The worker constantly pulls if it has to be terminated. Pulling in general is not the best, but the position where it lives is totally fine.

But I still would like to give this a light from a different perspective. Re-exposing this does not justify throwing the rest of our SimpleActionServer out.. And creating a whole new Child-class just for overwriting that
https://github.com/ros-planning/navigation2/blob/199ef58f04bce9da4fcf8800630d171665349015/nav2_util/include/nav2_util/simple_action_server.hpp#L78
Might be a little bit overkill.

I'm not totally hanging on this thing to live. I just saw that there was some lack in the stack to cover some basic things while writing tests for it.
An ending thought: Preemtion only gets triggered in Preemtion Cases (-> not for a first initial call and also not when the current goal finished)

@SteveMacenski
Copy link
Copy Markdown
Member

SteveMacenski commented Dec 10, 2020

So I thought it might be interesting to potential increase performance in some instances where the action-goal can be rejected based on very simple pre-checks.

Keep in mind that its "accepted" in a weird sense. Its accepted by the server, but gets shoved off into the preempting goal variables so its not directly doing anything until the is_preempt_requested is looked at by the host server and then accepted for preemption. So "accepted" is a strong word, that's the function name we have to implement, but the middleman simple action server doesn't directly put that preempted goal into practice, it gets stored for the host server to see if it should be used.

An ending thought: Preemtion only gets triggered in Preemtion Cases (-> not for a first initial call and also not when the current goal finished)

I think this is a discussion we should have as part of a design discussion for the preemption BT handling ticket you linked to. But I think we're in rough agreement that this solution containing simple action server changes is probably not the right one to fix the BT issue because we have to poll. It might be applicable to other servers, but as I mentioned, there's other options that work to keep it "simple".

@gramss
Copy link
Copy Markdown
Contributor Author

gramss commented Dec 10, 2020

I agree on all your points.

But, I still think that the application of a Simple Server is not able to add simple application specific checks that would prevent doing anything when it is already clear that the input is bad.

Also, there is currently no way to get the goal from the protected variable pending_handle_ without additional getters -> this also makes it not possible to check in the if(is_preempt_requested()) check to check the input of the goal before shifting it into current_handle_. And at that point the old current_handle_ is gone for sure, but your action_goal_field still has rubbish in it..

So it seems we would need to alter the SimpleActionServer for this anyhow, if we decide to go for it.


Side note on the preemt handeling and not starting a new work()-thread: at least in Bt_navigator this could be handled without altering SimpleActionServer.
The Preempt Check has this here:

https://github.com/ros-planning/navigation2/blob/7bc04a6c5f0f081292d04339ec1fcbd6d61bf669/nav2_bt_navigator/src/bt_navigator.cpp#L366

Here you could add logic what to do if the BT_XML-field is != the current one.. --> reloading the BT.
But uff.. this is called from a callback run by the bt_engine:
https://github.com/ros-planning/navigation2/blob/8bd158af79b942f4053206674a33b403e6c144e4/nav2_behavior_tree/src/behavior_tree_engine.cpp#L48

Reloading a BT while the old one is not halted yet. Should be doable somehow. But we should shift that discussion in the original topic.

Just found it mention-able what the current restrictions of the is_preempt_requested sub-function is. (At least for BT_Navigator related limitations)

@SteveMacenski
Copy link
Copy Markdown
Member

SteveMacenski commented Dec 11, 2020

But, I still think that the application of a Simple Server is not able to add simple application specific checks that would prevent doing anything when it is already clear that the input is bad.

I think regardless of the intent, breaking the design encapsulation of the simple action server to expose the callbacks fundamentally breaks the "simple action server" that this is. If you need those callbacks for any reason at all, then I think you should be using the actual action server which is always available and free for use. There's no requirement any action server is a simple action server in nav2, and I'm sure we'll end up adding some raw-action-servers in the future for very niche needs.

If this was a question of "I need this feature exposed" and this was the action server, then I'd 100% be on board with this proposal (and even probably make you expose even more). But since this is specifically a "simplified" wrapper, the design very specifically made it so that those callbacks were hidden from the user. The simple action server is not meant to be a 1:1 full replacement (hence the name) so you're right, there are going to be situations where this doesn't do everything you need. But if you're in that situation, then using the full action server makes alot of sense.


Those getters would be a good addition so you can inspect the new goal to know if you want to accept or reject it, that would be a good addition! I assumed those were already there. I don't 100% understand the sidebar below, I think this is a case of not enough prior context before proposing a fix (I'm not sure what you're working on solving here right now, it seems like it covers a few things but we should do 1 thing at a time). The current problem I think we're addressing is in the simple action server design to be able to inspect preemption requested goals. Once we have some alignment there, we can go into more specific things.

@gramss
Copy link
Copy Markdown
Contributor Author

gramss commented Dec 11, 2020

okay okay.. I think we are getting somewhere.

Just to recap:

  • we both want to keep the SimpleActionServer simple
  • this proposed re-exposed Callback would only increase complexity in the SimpleActionServer-Class and not the various Implementations -> As it is defined as optional, nobody notices it (also a reason why CI happy accepts it)
  • BUT we do not want a one size fits it all "Simple" Action server --> therefore we do not merge that

Things that we agreed on (pls confirm):

  • Adding a getter for pending_goal_ -> so we can check it during preemtion
  • All goals still get "accepted" -> but we cancel bad ones on multiple checks instead of one (in this instance: NavigateToPose, loadBT, preemtCheck + getter)

(personally, I would outweigh keeping more in line with the ActionServer Design than keeping our SimpleActionServer simple. Because I think that it just fails to comply the Design of an ActionServer, in this very instance. And implementing a whole new ActionServer-Impl from "scratch" is also not my preferred way, as there is some good stuff (preemption-handling) happening in our SimpleActionServer) --> But for now we shift the complexity into the bt_navigator

What this decision leaves open / next step:

  • What happens if a Preemption fails -> this was my sidenote stuff, right before falling asleep..
    I think we should keep that discussion going in a different issue. Which one do you propose?

Currently I see those open threads:

@SteveMacenski
Copy link
Copy Markdown
Member

Things that we agreed on (pls confirm):

Yes and I don't understand what " but we cancel bad ones on multiple checks instead of one (in this instance: NavigateToPose, loadBT, preemtCheck + getter)" means.

I don't see how any of this relates to #2010, that seems really off topic

@gramss
Copy link
Copy Markdown
Contributor Author

gramss commented Dec 11, 2020

I don't understand what " but we cancel bad ones on multiple checks instead of one (in this instance: NavigateToPose, loadBT, preemtCheck + getter)" means

This here would ensure that the rest of anything run after the initial goal was transmitted includes a valid XML.
With out this exposed Callback I have to check if the XML is valid at at least 2 or 3 places.

  1. PreemptCheck -> check if a goal is good before canceling a current one
  2. load_behaviortree -> right before loading the xml check if it is good (this exists) https://github.com/ros-planning/navigation2/blob/8bd158af79b942f4053206674a33b403e6c144e4/nav2_bt_navigator/src/bt_navigator.cpp#L155

And 1 also needs to run trough 2. This does not really add up as file-operations are usually not that heavy.
It's just not the pretties design.
But I guess the design integrity of SimpleActionServer outweighs the one of bt_navigator --> Coulde be "fixed" in a longer scope with #2010, see note down here


As #2010 rightly wants to split NavigateToPose from the BTActionServer implementation, it would be good if we consider stronger checks by design there. Probably even making it interesting to directly implement an ActionServer to better fit certain conditions. I might have an idea or to for further features for a BTActionServer, that might justify switching from SimpleActionServer to an own implementation.

@SteveMacenski
Copy link
Copy Markdown
Member

SteveMacenski commented Dec 14, 2020

Maybe we should close out this PR and move the conversations into their appropriate tickets. I'm having a very difficult time figuring out what you're trying to do from the context alone. This PR is very distracting from what I think you want to have be a more general conversation about how to address the preemption with a new or invalid BT within BT navigator. We haven't even had the discussion if allowing for it is even the appropriate action.

In general, I think having 2 different places to check the XML is fine. If anything, that's an argument to abstract out the XML validation to its own function so it can be used in both places. I don't see calling xml.good() twice being a reason for any architectural updates, you just need to split validation of the XML file into a function.

@SteveMacenski
Copy link
Copy Markdown
Member

I think #2261 supersedes this, so closing out the PR. We're welcome to reopen if there's progress or if we don't feel that it fills the niche - but I think it does (can check the goal for validity before processing)

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.

2 participants