This tutorial will guide you through the basic steps of using capabilities2. It will cover the following topics:
- Creating a capability using yaml model files.
- Run the capabilities server to serve the capability.
- Run the capabilities runner to execute the capability.
The tutorial will create a simple capability that prints "Hello, World!" to the console, using talker-listener nodes as a backend sub-system.
The capabilities server will serve the capability, and then spawn a runner that will execute the capability.
First, create a package to hold the capability. In this tutorial, we will create a package called hello_capability_world.
ros2 pkg create --build-type ament_cmake hello_capability_worldNext, create capability specification files in the package. These files will define the capability and the provider. Create 2 yaml files, talker_interface.yaml and talker_provider.yaml in the hello_capability_world package.
# talker_interface.yaml
name: talker
spec_type: interface
spec_version: 1.1
description: A simple talker capability that prints "Hello, World!" to the console.
interface:
topics:
"chatter":
type: std_msgs/msg/String
description: A topic that prints "Hello, World!" to the console.# talker_provider.yaml
name: talker
spec_type: provider
spec_version: 1.1
description: A simple talker provider that prints "Hello, World!" to the console.
implements: hello_capability_world/talker
runner: hello_capability_world::Talker # this is the C++ class that implements the providerAdd the following to the package.xml file in the hello_capability_world package.
<!-- within the export tag -->
<export>
<!-- add the capability files -->
<capability_interface>talker_interface.yaml</capability_interface>
<capability_provider>talker_provider.yaml</capability_provider>
</export>Create a C++ class that implements the provider. This class will be used to run the capability. Create a new file talker.cpp in the src directory of the hello_capability_world package. In this example, the capability publishes immediately when started, so the class can inherit from capabilities2_runner::NoTriggerRunner and be exported as a plugin.
// talker.cpp
#include "rclcpp/rclcpp.hpp"
#include "pluginlib/class_list_macros.hpp"
#include "capabilities2_runner/notrigger_runner.hpp"
#include "std_msgs/msg/string.hpp"
namespace hello_capability_world
{
class Talker : public capabilities2_runner::NoTriggerRunner
{
public:
Talker() = default;
void start(
rclcpp::Node::SharedPtr node,
const capabilities2_runner::runner_opts& run_config,
const std::string& bond_id) override
{
// init base class gets node info and stores the runner configuration
init_base(node, run_config);
// create a publisher to the chatter topic
auto chatter_pub = node_->create_publisher<std_msgs::msg::String>("chatter", 10);
emit_started(bond_id, "", param_on_started());
// send a message to the chatter topic
std_msgs::msg::String msg;
msg.data = "Hello, World!";
chatter_pub->publish(msg);
}
void stop(const std::string& bond_id, const std::string& instance_id = "") override
{
emit_stopped(bond_id, instance_id, param_on_stopped());
}
};
} // namespace hello_capability_world
// export the plugin
PLUGINLIB_EXPORT_CLASS(hello_capability_world::Talker, capabilities2_runner::RunnerBase)Follow the ROS2 pluginlib pattern to export the runner plugin.
<!-- plugins.xml -->
<library path="lib">
<class name="hello_capability_world::Talker" type="hello_capability_world::Talker" base_class_type="capabilities2_runner::RunnerBase">
<description>A simple talker provider that prints "Hello, World!" to the console.</description>
</class>
</library># CMakeLists.txt
add_library(${PROJECT_NAME} SHARED src/talker.cpp)
ament_target_dependencies(${PROJECT_NAME}
rclcpp
pluginlib
capabilities2_runner
std_msgs
)
pluginlib_export_plugin_description_file(${PROJECT_NAME} plugins.xml)The capabilities server is the main node in the capabilities2 package. It is responsible for managing capabilities, providers, and semantic interfaces. It is also responsible for starting and stopping capabilities.
Create a new config file in the hello_capability_world package. This file will define the capabilities that the server will serve.
# config/hello_capability_world.yaml
/**:
ros__parameters:
loop_rate: 5.0 # Hz
db_file: ~/.ros/capabilities/capabilities.sqlite3
rebuild: false # Set to true to rebuild the database
package_paths: # paths to search for capabilities
- /opt/ros/jazzy/share # default ROS2 package paths for Jazzy
- /home/ubuntu/colcon_ws/src
- /home/ubuntu/colcon_ws/src/capabilities2 # absolute paths for now, these paths assume the capabilities2 package is in the colcon workspace of the ubuntu user (devcontainer user)Run the capabilities server node and provide the config file with ROS parameters.
# run the capabilities server node with the config file
ros2 run capabilities2_server capabilities2_server_node --ros-args --params-file /path/to/hello_capability_world/config/hello_capability_world.yamlWe also need to run the talker-listener nodes as well. The talker-listener nodes are provided by the demo_nodes_cpp package. We can run the talker-listener nodes using the following command:
# in a new terminal
# run the listener node
ros2 run demo_nodes_cpp listenerNow that the capabilities server is running, we can run the capabilities runner to execute the capability.
# in a new terminal
# run the capabilities runner using a service call
ros2 service call /capabilities/start_capability capabilities2_msgs/srv/StartCapability "{capability: 'hello_capability_world/talker', provider: 'hello_capability_world/talker'}"The capabilities runner will start the capability and print "Hello, World!" to the console of the listener node.