Coding Examples: Creating a custom console plugin¶
In this tutorial, we will write a console plugin with a command to print a Hello world
message and a command to interact with a VI map.
The code that is produced in this tutorial can be downloaded from here.
Creating a package for your plugin¶
First, create a new package:
cd ~/maplab_ws/src/
mkdir hello-world-plugin
cd hello-world-plugin
mkdir src
Create the following file and save it as CMakeLists.txt
:
cmake_minimum_required (VERSION 2.8)
project(hello_world_plugin)
find_package(catkin_simple REQUIRED)
catkin_simple(ALL_DEPS_REQUIRED)
add_definitions(-fPIC -shared)
cs_add_library(${PROJECT_NAME} src/hello-world-plugin.cc)
create_console_plugin(${PROJECT_NAME})
cs_install()
cs_export()
The important call here is create_console_plugin
which ensures that this package will be listed as a console plugin.
Also create a file package.xml
with the following contents:
<?xml version="1.0" encoding="UTF-8"?>
<package format="2">
<name>hello_world_plugin</name>
<version>0.0.0</version>
<description>A plugin to show how to add new console plugins</description>
<maintainer email="your-email@domain.tld">Maplab user</maintainer>
<license>Apache 2.0</license>
<buildtool_depend>catkin_simple</buildtool_depend>
<buildtool_depend>catkin</buildtool_depend>
<depend>console_common</depend>
</package>
The console plugin needs to depend on the package console_common
, everything else is free.
Creating a plugin and adding commands¶
Finally, we can take care of the C++ code. Create a file src/hello-world-plugin.cc
:
#include <iostream>
#include <string>
#include <map-manager/map-manager.h>
#include <vi-map/vi-map.h>
#include <console-common/console.h>
// Your new plugin needs to derive from ConsolePluginBase.
// (Alternatively, you can derive from ConsolePluginBaseWithPlotter if you need
// RViz plotting abilities for your VI map.)
class HelloWorldPlugin : public common::ConsolePluginBase {
public:
// Every plugin needs to implement a getPluginId function which returns a
// string that gives each plugin a unique name.
std::string getPluginId() const override {
return "hello_world_plugin";
}
// The constructor takes a pointer to the Console object which we can forward
// to the constructor of ConsolePluginBase.
HelloWorldPlugin(common::Console* console)
: common::ConsolePluginBase(console) {
// You can add your commands in here.
addCommand(
{"hello_world", "hello"}, // Map "hello_world" and "hello" to this
// command.
[this]() -> int { // Function to call when this command is entered.
// This function can do anything you want. Check the other plugins
// under ~/maplab_ws/src/maplab/console-plugins for more examples.
// Here, we just print a message to the terminal.
std::cout << "Hello world!" << std::endl;
// Every console command returns an integer, you can take one from
// the CommandStatus enum. kSuccess returns everything is fine.
// Other commonly used return values are common::kUnknownError and
// common::kStupidUserError.
return common::kSuccess;
},
// This is the description of your command. This will get printed when
// you run `help` in the console.
"This command prints \"Hello World!\" to the console.",
// This specifies the execution method of your command. For most
// commands, it is sufficient to run them in sync with
// common::Processing::Sync.
common::Processing::Sync);
}
};
// Finally, call the MAPLAB_CREATE_CONSOLE_PLUGIN macro to create your console
// plugin.
MAPLAB_CREATE_CONSOLE_PLUGIN(HelloWorldPlugin);
Interfacing with a VI map¶
Add the following dependencies to your package.xml
:
<depend>map_manager</depend>
<depend>vi_map</depend>
In src/hello-world-plugin.cc
, add the following includes:
#include <string>
#include <map-manager/map-manager.h>
#include <vi-map/vi-map.h>
Then you can add your new command in the constructor of your previously created class:
HelloWorldPlugin(common::Console* console)
: common::ConsolePluginBase(console) {
...
addCommand(
{"my_vi_map_command"},
[this]() -> int {
// Get the currently selected map.
std::string selected_map_key;
// This function will write the name of the selected map key into
// selected_map_key. The function will return false and print an error
// message if no map key is selected.
if (!getSelectedMapKeyIfSet(&selected_map_key)) {
return common::kStupidUserError;
}
// Create a map manager instance.
vi_map::VIMapManager map_manager;
// Get and lock the map which blocks all other access to the map.
vi_map::VIMapManager::MapWriteAccess map =
map_manager.getMapWriteAccess(selected_map_key);
// Now run your algorithm on the VI map.
// E.g., we can get the number of missions and print it.
const size_t num_missions = map->numMissions();
std::cout << "The VI map " << selected_map_key << " contains "
<< num_missions << " missions." << std::endl;
return common::kSuccess;
},
"This command will run an awesome VI map algorithm.",
common::Processing::Sync);
}
Testing your plugin¶
After you have created your plugin, you can compile your plugin:
catkin build hello_world_plugin
and start maplab:
rosrun maplab_console maplab_console -v=1
With -v=1
, you set the default verbosity level to 1. This is useful because this will print a list of all loaded plugins on program start, so you can check if your plugin is loaded correctly. You should see something like this in the output:
I1124 13:16:10.916294 26783 maplab-console.cc:100] RVIZ visualization initialized!
...
I1124 13:16:10.916895 26783 maplab-console.cc:126] Installed plugin hello_world_plugin from /home/user/catkin_ws/devel/lib/libhello_world_plugin.so.
...
Now, in the maplab console, you can check help
to see a list of all installed plugins. Entering help --plugin hello_world_plugin
will give you help for all commands in your new plugin:
maplab <>:/$ help --plugin hello_world_plugin
Showing help for plugin hello_world_plugin.
hello_world, hello
This command prints "Hello World!" to the console.
my_vi_map_command
This command will run an awesome VI map algorithm.
You can also call your new command and see that it works as intended:
maplab <>:/$ hello_world
Hello world!
Or if you have a map loaded, you can use the other command:
maplab <my_map>:/$ my_vi_map_command
The VI map my_map contains 1 missions.
Further reading¶
You can now extend your package to contain arbitrary code in your commands. E.g., you may want to make a console command out of this example.
If you’re interested to learn more about the inner workings of the plugin system, check out this article.