大纲
- 初始化环境
- 创建Package
- 代码
- 添加依赖(package.xml)
- 修改编译描述
- find_package寻找依赖库
- 指定代码路径和编译类型(可执行文件/动态库)
- 链接依赖的库
- 完整文件
- 编译
- 测试
- 总结
- 参考资料
之前我们看到ROS2中,有的Node的实现逻辑存在于动态库中,而动态库又有隐式加载和手动加载等几种模式。这些例子都比较复杂。本文将介绍如何从0到1创建一个最简单的工程,其Node都在可执行文件中,以保证过程的清晰。
初始化环境
在《Robot Operating System——深度解析自动隐式加载动态库的运行模式》一文中,我们展现了ROS2可执行文件的链接指令。可以看到它依赖了很多ROS2环境相关的动态库,所以我们在创建工程之前也要初始化环境。
source /opt/ros/jazzy/setup.bash
关于环境的安装可以参见《Robot Operating System——Ubuntu上以二进制形式安装环境》。
创建Package
ros2 pkg create --build-type ament_cmake --license Apache-2.0 two_node_pipeline
可以看到ros2帮我们创建好了相关的目录结构
剩下的事就是我们要填充这个工程。
代码
在two_node_pipeline/src目录下新建一个main.cpp文件。
代码我们直接借用https://github.com/ros2/demos/blob/jazzy/intra_process_demo/src/cyclic_pipeline/cyclic_pipeline.cpp的源码。它直接在一个可执行文件中编译了两个Node。
// Copyright 2015 Open Source Robotics Foundation, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <chrono>
#include <cinttypes>
#include <cstdio>
#include <memory>
#include <string>
#include <utility>
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/int32.hpp"
using namespace std::chrono_literals;
// Node that produces messages.
struct Producer : public rclcpp::Node
{
Producer(const std::string & name, const std::string & output)
: Node(name, rclcpp::NodeOptions().use_intra_process_comms(true))
{
// Create a publisher on the output topic.
pub_ = this->create_publisher<std_msgs::msg::Int32>(output, 10);
std::weak_ptr<std::remove_pointer<decltype(pub_.get())>::type> captured_pub = pub_;
// Create a timer which publishes on the output topic at ~1Hz.
auto callback = [captured_pub]() -> void {
auto pub_ptr = captured_pub.lock();
if (!pub_ptr) {
return;
}
static int32_t count = 0;
std_msgs::msg::Int32::UniquePtr msg(new std_msgs::msg::Int32());
msg->data = count++;
printf(
"Published message with value: %d, and address: 0x%" PRIXPTR "\n", msg->data,
reinterpret_cast<std::uintptr_t>(msg.get()));
pub_ptr->publish(std::move(msg));
};
timer_ = this->create_wall_timer(1s, callback);
}
rclcpp::Publisher<std_msgs::msg::Int32>::SharedPtr pub_;
rclcpp::TimerBase::SharedPtr timer_;
};
// Node that consumes messages.
struct Consumer : public rclcpp::Node
{
Consumer(const std::string & name, const std::string & input)
: Node(name, rclcpp::NodeOptions().use_intra_process_comms(true))
{
// Create a subscription on the input topic which prints on receipt of new messages.
sub_ = this->create_subscription<std_msgs::msg::Int32>(
input,
10,
[](std_msgs::msg::Int32::UniquePtr msg) {
printf(
" Received message with value: %d, and address: 0x%" PRIXPTR "\n", msg->data,
reinterpret_cast<std::uintptr_t>(msg.get()));
});
}
rclcpp::Subscription<std_msgs::msg::Int32>::SharedPtr sub_;
};
int main(int argc, char * argv[])
{
setvbuf(stdout, NULL, _IONBF, BUFSIZ);
rclcpp::init(argc, argv);
rclcpp::executors::SingleThreadedExecutor executor;
auto producer = std::make_shared<Producer>("producer", "number");
auto consumer = std::make_shared<Consumer>("consumer", "number");
executor.add_node(producer);
executor.add_node(consumer);
executor.spin();
rclcpp::shutdown();
return 0;
}
添加依赖(package.xml)
package.xml 是 ROS 2 包的元数据文件,定义了包的基本信息和依赖关系。
打开two_node_pipeline/package.xml,添加代码中include头文件(#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/int32.hpp"
)所依赖的其他库。
<depend>rclcpp</depend>
<depend>std_msgs</depend>
完整文件如下
<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
<name>two_node_pipeline</name>
<version>0.0.0</version>
<description>TODO: Package description</description>
<maintainer email="f304646673@gmail.com">fangliang</maintainer>
<license>Apache-2.0</license>
<buildtool_depend>ament_cmake</buildtool_depend>
<depend>rclcpp</depend>
<depend>std_msgs</depend>
<test_depend>ament_lint_auto</test_depend>
<test_depend>ament_lint_common</test_depend>
<export>
<build_type>ament_cmake</build_type>
</export>
</package>
修改编译描述
find_package寻找依赖库
再打开two_node_pipeline/CMakeLists.txt中添加上述头文件(#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/int32.hpp"
)所依赖的库
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)
在 CMake 中,find_package 命令用于查找并加载外部包或库。它的主要作用如下:
-
查找包:find_package 会在系统中查找指定的包或库,并加载其配置文件。这些配置文件通常包含包的路径、库文件、头文件等信息。
-
设置变量:如果找到包,find_package 会设置一些变量,这些变量可以在后续的 CMake 脚本中使用。例如,<PackageName>_FOUND 变量会被设置为 TRUE,表示找到了包。
-
导入目标:一些包会定义 CMake 导入目标(imported targets),这些目标可以直接在 target_link_libraries 等命令中使用。
指定代码路径和编译类型(可执行文件/动态库)
设定编译结果名称
set(EXECUTABLE_NAME "two_node_pipeline")
指定编译结果是可执行文件
# Collect all source files in this directory
file(GLOB SOURCES "src/*.cpp")
# Add the executable target
add_executable(${EXECUTABLE_NAME} ${SOURCES})
链接依赖的库
最后将代码中用到的库(#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/int32.hpp"
)链接到可执行文件中。
ament_target_dependencies(${EXECUTABLE_NAME} rclcpp std_msgs)
完整文件
cmake_minimum_required(VERSION 3.8)
project(two_node_pipeline)
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra -Wpedantic)
endif()
# find dependencies
find_package(ament_cmake REQUIRED)
# uncomment the following section in order to fill in
# further dependencies manually.
# find_package(<dependency> REQUIRED)
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)
set(EXECUTABLE_NAME "two_node_pipeline")
# Collect all source files in this directory
file(GLOB SOURCES "src/*.cpp")
# Add the executable target
add_executable(${EXECUTABLE_NAME} ${SOURCES})
ament_target_dependencies(${EXECUTABLE_NAME} rclcpp std_msgs)
if(BUILD_TESTING)
find_package(ament_lint_auto REQUIRED)
# the following line skips the linter which checks for copyrights
# comment the line when a copyright and license is added to all source files
set(ament_cmake_copyright_FOUND TRUE)
# the following line skips cpplint (only works in a git repo)
# comment the line when this package is in a git repo and when
# a copyright and license is added to all source files
set(ament_cmake_cpplint_FOUND TRUE)
ament_lint_auto_find_test_dependencies()
endif()
ament_package()
编译
cd two_node_pipeline/build
cmake ..
make
测试
./two_node_pipeline
总结
- 初始化环境
source /opt/ros/jazzy/setup.bash
,以保证ROS2的基础动态库路径在系统PATH中。 - 通过`ros2 pkg create --build-type ament_cmake --license Apache-2.0 <YOUR-Folder-Name>'创建目录结构和基础文件。
- 填充代码。
- package.xml中添加代码中显式依赖的库。
- CMakeLists.txt中寻找和链接代码中显式依赖的库。
参考资料
- https://docs.ros.org/en/rolling/Tutorials/Beginner-Client-Libraries/Creating-A-Workspace/Creating-A-Workspace.html#new-directory