OmniPlan is a ROS 2 framework for automated task planning and execution. It integrates multiple classical planners with flexible execution mechanisms including direct action implementation, state machines and behavior trees. The framework supports both knowledge base and knowledge graph approaches for state management, enabling planning solutions for robotic applications. Finally, OmniPlan can be extended through the creation of new plugins to integrate new planners and new knowledge sources.
- Plugin Architecture: Extensible design using ROS 2 pluginlib for omni customization.
- Multiple PDDL Planners: Support for POPF, SMTP and VHPOP planners and VAL plan validator.
- Flexible Execution: Execute plans using direct actions, YASMIN state machines or Behavior Trees.
- Knowledge Management: Choose between knowledge base or knowledge graph approaches or integrate your own implementation.
- ROS 2 Native: Built on ROS 2 with proper message interfaces.
# Clone this repo
cd ~/ros2_ws/src
git clone https://github.com/mgonzs13/omni_plan
# Install dependecies
cd ~/ros2_ws
vcs import src < src/omni_plan/dependencies.repos
rosdep install --from-paths src --ignore-src -r -y
# SMTPlan+ dependency
sudo apt install libz3-dev -y
# Colin and OPTIC dependecy
sudo apt install coinor-libcbc3 -y
# Optional: You may need to create symbolic link
sudo ln -s /usr/lib/x86_64-linux-gnu/libCbc.so.3.10.11 /usr/lib/x86_64-linux-gnu/libCbc.so.3
# Build the workspace
colcon build --symlink-installTo run the tests:
colcon test --executor sequential --packages-select omni_plan omni_plan_knowledge_base omni_plan_knowledge_graph omni_plan_popf omni_plan_vhpop omni_plan_smtp omni_plan_optic omni_plan_lpg omni_plan_colin omni_plan_mrta omni_plan_val omni_plan_dispatcher omni_plan_yasmin omni_plan_bt omni_plan_tests
colcon test-result --verboseeasy_plan_2_1.mp4
The framework includes several demo packages showcasing different planning and execution approaches:
ros2 launch omni_plan_demos popf_kg_demo.launch.pyros2 launch omni_plan_demos popf_kb_demo.launch.pysmtp_kb_demo.launch.py/smtp_kg_demo.launch.py: SMTP planner demosvhpop_kb_demo.launch.py/vhpop_kg_demo.launch.py: VHPOP planner demos
ros2 run omni_plan_demos knowledge_graph_demoDemonstrates concurrent action execution using the knowledge graph. Three robots pick up parts in parallel, then a convergence step assembles them once all predecessors have completed.
ros2 launch omni_plan_demos popf_assembly_demo.launch.pyThe PDDL domain models pick-up (per-robot) and assemble (requires all
parts) as durative actions with OVER_ALL battery conditions so the planner
schedules them as a true parallel graph.
ros2 run omni_plan_demos assembly_demoThe omni_plan_tui package provides a terminal-based monitoring interface for
active plan execution. It subscribes to three topics and renders a live,
colour-coded view in the terminal using ncurses.
ros2 run omni_plan_tui omni_plan_tui_node| Tab | Key | Description |
|---|---|---|
| Plan Execution | 1 |
Level-grouped graph view of all actions with real-time status icons and elapsed times |
| FSM State | 2 |
Current YASMIN state machine state and full hierarchy |
| Action Catalog | 3 |
Complete list of loaded plugin actions with parameters, conditions and effects |
| Key | Action |
|---|---|
q / Q |
Quit |
1 / 2 / 3 |
Jump to Plan / FSM / Actions tab |
Tab / ] |
Next tab |
[ |
Previous tab |
↑ / ↓ |
Scroll one row |
PgUp / PgDn |
Scroll ten rows |
Home / End |
Jump to top / bottom |
| Mouse click (tab bar) | Switch to clicked tab |
| Topic | Message type | QoS |
|---|---|---|
/omni_plan/actions_info |
omni_plan_msgs/ActionInfoArray |
Transient-local (latched) |
/omni_plan/plan_execution |
omni_plan_msgs/PlanExecutionStatus |
Default |
/fsm_viewer |
yasmin_msgs/StateMachine |
Default |
OmniPlan uses a plugin-based architecture that allows developers to extend the framework by creating new planners, plan validators, and actions. All plugins are loaded using ROS2's pluginlib system.
PDDL managers handle domain and problem generation from action definitions and manage the current world state. They support different state representation approaches (e.g., knowledge base vs. knowledge graph). Inherit from omni_plan::PddlManager:
#include "omni_plan/pddl_manager.hpp"
class MyPddlManager : public omni_plan::PddlManager {
public:
MyPddlManager() : PddlManager() {}
protected:
// Generate PDDL domain and problem from current state
std::pair<omni_plan::pddl::Domain, omni_plan::pddl::Problem>
get_pddl() const override {
// Implement your state representation logic here
// Return a pair of Domain and Problem objects
}
// Check if there are any goals to achieve
bool has_goals() const override {
// Query your state representation for pending goals
}
// Clear all current goals
bool clear_goals() const override {
// Clear goals from your state representation
}
// Check if a predicate exists in the current state
bool predicate_exists(const omni_plan::pddl::Predicate &predicate) const override {
// Query your state representation for the predicate
}
// Check if a predicate is part of the goal conditions
bool predicate_is_goal(const omni_plan::pddl::Predicate &predicate) const override {
// Check if the predicate is in the goals
}
// Apply a single effect to the current state
void apply_effect(const omni_plan::pddl::Effect &exp) override {
// Update your state representation with the effect
// May add or delete predicates depending on the effect type
}
};To create a new planner, inherit from the omni_plan::Planner base class and implement the required virtual methods:
#include "omni_plan/planner.hpp"
class MyPlanner : public omni_plan::Planner {
public:
MyPlanner() : Planner() {}
protected:
// Generate plan from PDDL files
std::string generate_plan(const std::string domain_path,
const std::string problem_path) const override {
// Implement your planning algorithm here
// Return the plan as a string in PDDL format
}
// Check if the plan output indicates a valid solution
bool has_solution(const std::string &plan_str) const override {
// Analyze the planner output to determine if a solution was found
}
// Optional: Parse action lines from the plan output
std::pair<std::string, std::vector<std::string>>
parse_action_line(std::string line) const override {
// Extract action name and parameters from a line of planner output
}
// Optional: Extract lines containing actions from the complete plan output
std::vector<std::string>
get_lines_with_actions(const std::string &plan_str) const override {
// Filter and return only the lines that represent actions
}
};Register your planner in a plugins.xml file and export it using PLUGINLIB_EXPORT_CLASS.
Plan validators verify that generated plans are correct. Inherit from omni_plan::PlanValidator:
#include "omni_plan/plan_validator.hpp"
class MyValidator : public omni_plan::PlanValidator {
public:
MyValidator() : PlanValidator() {}
protected:
// Validate plan against domain and problem
bool validate_plan(const std::string &domain_path,
const std::string &problem_path,
const std::string &plan_path) const override {
// Implement validation logic using your preferred validator
}
// Convert Plan object to PDDL string format
std::string parse_pddl(const omni_plan::pddl::Plan &plan) const override {
// Convert the internal Plan representation to PDDL format
}
};A PlanDispatcher plugin controls how the actions of a plan are executed once the planning graph has been built. Two built-in strategies are provided (SequentialPlanDispatcher and ParallelPlanDispatcher). You can create your own by:
- Inheriting from
omni_plan::PlanDispatcherand implementingdispatch_actions(). - Registering it as a pluginlib plugin with base class
omni_plan::PlanDispatcher.
// my_dispatcher/include/my_dispatcher/my_plan_dispatcher.hpp
#include "omni_plan/plan_dispatcher.hpp"
namespace my_dispatcher {
class MyPlanDispatcher : public omni_plan::PlanDispatcher {
public:
MyPlanDispatcher() : omni_plan::PlanDispatcher() {}
protected:
omni_plan::pddl::ActionStatus dispatch_actions(
const std::vector<omni_plan::pddl::GraphNode::Ptr> &all_nodes) override {
for (const auto &node : all_nodes) {
if (this->is_canceled()) {
return omni_plan::pddl::ActionStatus::CANCELED;
}
this->set_node_status(node->node_num,
omni_plan_msgs::msg::PlanActionStatus::RUNNING);
this->publish_exec_status(
omni_plan_msgs::msg::PlanExecutionStatus::RUNNING);
auto action = node->action.action;
this->push_current_action(action);
omni_plan::pddl::ActionStatus result =
this->run_node_action(node, action);
this->remove_current_action(action);
if (result != omni_plan::pddl::ActionStatus::SUCCEEDED) {
if (this->cancel_on_abort_) {
this->cancel_plan();
}
return result;
}
}
this->clear_current_actions();
return omni_plan::pddl::ActionStatus::SUCCEEDED;
}
};
} // namespace my_dispatcher| Helper | Description |
|---|---|
is_canceled() |
Returns true if cancel_plan() has been called. |
cancel_plan() |
Cancels all running actions and sets the cancellation flag. |
run_node_action(node, action) |
Applies PDDL effects, runs the action, and rolls back on failure. Returns ActionStatus. |
push_current_action(action, use_cache) |
Registers an action as currently running (enables cancellation). Returns the action instance to use — if use_cache=true a cached copy is returned when the action is already in use (for parallel branches). |
remove_current_action(action) |
Un-registers a completed action. |
clear_current_actions() |
Clears all tracked actions (call at the end of dispatch). |
set_node_status(node_num, status) |
Updates the per-node execution status for publishing. |
publish_exec_status(overall) |
Publishes the current status snapshot on /omni_plan/plan_execution. |
acquire_cached_action(action) |
Returns an idle clone from the action pool (or creates one). |
release_cached_action(action) |
Returns a clone to the pool for future reuse. |
| Flag | Default | Description |
|---|---|---|
cancel_on_abort_ |
false |
When true, call cancel_plan() automatically whenever an action aborts. |
cancel_on_new_goals_ |
false |
When true, monitor the PDDL manager for new goals and cancel execution if any appear. |
Both flags are exposed as ROS parameters: plan_dispatcher.cancel_on_abort and plan_dispatcher.cancel_on_new_goals.
Actions define the executable behaviors in your planning domain. All actions inherit from omni_plan::pddl::Action and must implement the run and cancel methods. All action types must be registered in a plugins.xml file and exported using the appropriate PLUGINLIB_EXPORT_CLASS macro.
For simple actions implemented directly in C++:
#include "omni_plan/pddl/action.hpp"
class MyAction : public omni_plan::pddl::Action {
public:
MyAction()
: Action("my_action", {
{"param1", "type1"},
{"param2", "type2"}
}) {
// Add preconditions
this->add_condition(omni_plan::pddl::START, "predicate_name",
{"param1", "param2"});
// Add effects
this->add_effect(omni_plan::pddl::END, "predicate_name",
{"param1"}, true); // true for negated effect
}
omni_plan::pddl::ActionStatus run(const std::vector<std::string> ¶ms) override {
// Implement your action execution logic
// Return SUCCEEDED, CANCELED, or ABORTED
return omni_plan::pddl::ActionStatus::SUCCEEDED;
}
void cancel() override {
// Handle action cancellation
}
};For actions that use YASMIN state machines defined programmatically:
#include "omni_plan_yasmin/yasmin_action.hpp"
class MyYasminAction : public omni_plan_yasmin::YasminAction {
public:
MyYasminAction()
: YasminAction("my_action", {
{"param1", "type1"},
{"param2", "type2"}
}) {
// Define PDDL conditions and effects as in regular actions
// Build your YASMIN state machine
this->add_state("STATE1", std::make_shared<MyState1>());
this->add_state("STATE2", std::make_shared<MyState2>());
this->add_transition("STATE1", "STATE2", "outcome1");
// ... configure state machine
}
};For actions using YASMIN state machines defined in XML files:
#include "omni_plan_yasmin/yasmin_factory_action.hpp"
class MyYasminFactoryAction : public omni_plan_yasmin::YasminFactoryAction {
public:
MyYasminFactoryAction()
: YasminFactoryAction("my_action",
{{"param1", "type1"}, {"param2", "type2"}},
"/path/to/state_machine.xml") {
// Define PDDL conditions and effects
// The state machine is loaded from the XML file
}
};For actions implemented as Behavior Trees:
#include "omni_plan_bt/bt_action.hpp"
class MyBtAction : public omni_plan_bt::BtAction {
public:
MyBtAction()
: BtAction("my_action",
{{"param1", "type1"}, {"param2", "type2"}},
"/path/to/behavior_tree.xml") {
// Define PDDL conditions and effects
// The behavior tree is loaded from the XML file
}
};