I spent this week mostly learning about Behavior Trees, especially in robotics using ROS. A behavior tree is a structure that defines the different tasks of an agent and how to switch between them. Although this is a relatively new concept (for me, at least), its a familiar concept to engineers in the game industry, where it originates from. Game designers use behavior trees to describe AI for Non-Player Characters or NPCs. Its definitely a powerful way of describing complex robot behaviors and a great way to complement other similar methods used in the robotics world such as FSMs and Heirarchial FSMs (an amazing comparison can be found in these slides by K.C. Chang and David Zhu). To start learning, I decided to look at BehaviorTree.cpp, an excellent open-source library by Davide Faconti, written in C++. Since I was new to behavior trees in general, I was a bit apprehensive of diving into the implementation before studying some theory, but I’m glad I went to the library documentation first because it not only explains how to implement behavior trees, it also provides an excellent introduction to people new to the topic. Using the concepts explained in the documentation, as well as a behavior tree editor called Groot (I guess this now explains the thumbnail image of Disney’s ridiculously cute baby Groot robot), I was able to define my first behavior tree - a patrol sequence for the robot that loads a set of waypoints and drives to each of them sequentially until it fails. For this, I also used this example by Sebastian Castro (also known as Robotic Sea Bass), explained in detail in his amazing blog post. I first started off with a simple behavior tree structure on Groot:
Once this behavior tree was designed, Groot saves it in the form of an XML file. This XML file is then loaded into the C++ node at runtime, using which a tree is created. The custom nodes are defined in a separate navigation_behaviors.cpp source file. I reused the behaviors made by Sebastian and added some modifications. My custom behaviors are: SetWaypoints (sets the number of waypoints and loads them to the back of stack), GetWaypointFromQueue (pops waypoints from the front of the stack and returns the coordinates. If the stack is empty, reloads the stack) and finally GoToGoal (takes the coordinates and passes them to move_base. Returns the result of move_base). The KeepRunningUntilFailure node keeps ticking the sequence continuously until a failure is returned from one of the child nodes.
With the ROS node was all set up, it was now time to test. Groot provides the functionality of realtime monitoring of the status of the behavior tree and its nodes. This needs ZeroMQ to communicate between the ROS node and Groot. Unfortunately, due to some version mismatch, I was unable to get this working. I do plan on fixing this in the future, but for now, I decided on using the tried-and-tested debug print statements. In the following video, you can see print statements on the terminal while the robot follows the sequence of waypoints:
For my next steps, I’m excited to learn about designing robot behaviors even further, especially in the ROS context. While Sebastian in his blog mentions py_trees and py_trees_ros, I haven’t been able to install/run them correctly on the Raspberry Pi. The terminology used also seems quite different to the BehaviorTree.cpp implementation, and from the looks of it, there seems to be more support/development focused towards ROS2. For now, I am sufficiently happy with BehaviorTree.cpp and since its being supported in ROS2 as well, I don’t see a reason to stop using it or switching over to Python (also a programming language I’m not entirely fluent in). So, for my next challenge - I currenly have a node with nested if conditions to switch between different modes (manual, autonomous, assisted teleop) and it would be great to try and design it using state machines or behavior trees. I also want to explore how FSMs are implemented in ROS and from a brief survey, FlexBE looks like the way to start.