sea-bass / turtlebot3_behavior_demos

Example repository for autonomous behaviors using TurtleBot3, as well as Docker workflows in ROS based projects.
MIT License
270 stars 53 forks source link

Questions: BT run by a FSM/HSM: how to leave BT properly ? #42

Closed Lecrapouille closed 5 months ago

Lecrapouille commented 8 months ago

Hello sir!

I read with pleasure your article https://robohub.org/introduction-to-behavior-trees I greatly appreciate how a BT can be depicted by an FSM. I have two questions and a small remark:

fsm

Since the state machine time is discretized, several events can happen at the same time. Here BatteryKO event and success. To know which state to go, we have to make choice (guards) mutually exclusive: for example success and battery ok for going to closeGrip state. This seems obvious to veterans, but this does not hurt to tell it explicitly for beginners in your article.

bt

in FSM/HSM what is your suggestion for implementing code leaving/interrupting the BehaviorTree wrorkflow when i.e. the battery KO event is triggered and switching to the next FSM state ? I hope not to be wrong whe saying you have not implemented a basic example HSM with BT. If we made things not correctly, the BT state will still be in the RUNNING state of the current node. We have to reset states of the tree correctly. What do you suggest from your experience ?

What I'm thinking is update_behavior_tree() called by the state nominal activity in pseudocode (there are lot of libs with a more modern way separating the logic of transition from the business code, i.e. https://github.com/erikzenker/hsm):

void NominalState::step()
{ 
   status = update_behavior_tree() ;
   if (status == FAILURE)
   {
       // leave the state
       next_state = GoToFailureState;
   }
   else if (status == SUCCESS)
   {
       // leave the state
       next_state = GoToSuccessState;
   }
   else
   {
      // stay in the same state
   }
}

BT::NodeStatus update_behavior_tree()
{
   // Tick the behavior tree.
   BT::NodeStatus tree_status = tree_.tickOnce();
   if (tree_status == BT::NodeStatus::RUNNING) {
         return tree_status;
   }
   ...

The implementation with the battery would be:

void NominalState::step()
{ 
   if (battery KO)
   {
       // reset the tree ?
       tree_.haltTree(); 
       // leave the state
       next_state = GoToChargeState;
   }
   else
   {
       status = update_behavior_tree() ;
       if (status == FAILURE)
       {
          // leave the state
          next_state = GoToFailureState;
       }
       else if (status == SUCCESS)
       {
          // leave the state
          next_state = GoToSuccessState;
       }
       else
       {
          // stay in the same state
       }
   }
}

Have you tried BT with Petri net (GRAFCET aka SFC) instead of HSM ? Running several BT in concurrency ?

Thank you in advance !

EDIT: I realized that FSM libs (boost MSM/boost statecharts/tinyfsm ...) does not offer a tick() method to perform some state activity (activity = long term action). The only action are triggered by transitions. So I cannot call BT::TickOnce(). So finally, it's not so easy to mix BT with FSM. I made a basic FSM with switch case and BT::haltTree(); has to be called.

sea-bass commented 6 months ago

I think you're on the right track here.

In most behavior tree implementations, there are mechanisms to halt a tree (which should, in turn, halt all the active nodes). So for each node that is potentially executing a long-running action -- for example, calling out to a ROS service/action server -- that halt implementation should be responsible for canceling the thing it's waiting for.

Then, in FSM implementations there often are hooks to insert code that can be executed either when a state transition is happening, or at a state-related event (e.g., exiting a state).

The way I might tackle this is for the FSM to have a pointer of the current active Behavior Tree (or possibly, list of BTs if you are executing many concurrently). So whenever you hit some kind of state transition that warrants changing the behavior tree(s), you ensure to halt the tree(s) before switching.

You may also get "lucky" and be in a situation where the simple act of destroying the active behavior tree(s), thereby invoking their nodes' destructors, is enough to halt the tree and clean up... but I wouldn't rely on that myself, and try to explicitly halt the BTs first.


Regarding the second question

  1. No I have not tried anything related to Petri Nets before. Though in last year's ROSCon (2023) there was a nice talk about this: https://github.com/thorstink/Symmetri
  2. I have also not tried to run multiple BTs concurrently. I would try as hard as possible to have a single BT and rely on Parallel control nodes as much as possible. One exception might be a centralized multi-robot scenario where you can basically guarantee that the multiple BTs currently active won't mess with each others' state.
Lecrapouille commented 6 months ago

Thank you ! I know Symmetri, I'm in touch with the dev