2、工作空间与功能包:创建colcon工作空间,编写第一个C++/Python节点
好,咱们正式开始动手了。这一章,我带你从零搭建一个ROS2工作空间,再写两个最基础的节点——一个用C++,一个用Python。说白了,这就是你ROS2生涯的“Hello World”。
我个人习惯把工作空间比作一个“项目文件夹”。你所有的代码、依赖、配置文件,都整整齐齐地放在里面。colcon就是那个帮你把代码编译成可执行文件的工具。嗯,这里要注意,ROS2默认用的就是colcon,不是catkin了,别搞混。
2.1 创建colcon工作空间
打开终端,我们一步步来。先创建一个目录,然后初始化它。
mkdir -p ~/ros2_ws/src
cd ~/ros2_ws
colcon build
就这么简单?对,就这么简单。我第一次用colcon的时候,也觉得太轻量了。build完之后,你会看到三个新目录:build、install、log。它们各自干什么用?
| 目录 | 作用 |
|---|---|
| build | 编译中间文件,一般不用管 |
| install | 编译产物,包括可执行文件、库、配置文件 |
| log | 编译日志,出错了来这里查 |
~/ros2_ws目录下重新colcon build。我刚开始经常忘了这步,结果运行的一直是老版本,排查了半天……你想想看,多冤。
2.2 创建功能包
工作空间有了,接下来创建功能包。功能包是ROS2里最小的组织单元。一个包可以包含节点、消息定义、配置文件等等。
我们创建一个C++包和一个Python包,分别演示。
2.2.1 创建C++功能包
cd ~/ros2_ws/src
ros2 pkg create my_cpp_pkg --build-type ament_cmake --dependencies rclcpp std_msgs
这条命令干了三件事:
- 创建了
my_cpp_pkg目录 - 指定了编译类型为
ament_cmake(C++专用) - 添加了依赖
rclcpp(ROS2客户端库)和std_msgs(标准消息类型)
打开看看,里面有个src目录,还有个CMakeLists.txt和package.xml。这两个文件是功能包的“身份证”。
2.2.2 创建Python功能包
ros2 pkg create my_py_pkg --build-type ament_python --dependencies rclpy std_msgs
Python包的结构略有不同。它没有src目录,而是直接有一个和包名同名的Python模块目录。里面放着__init__.py和你的节点代码。
我记得刚开始用Python包时,总找不到入口点。后来才明白,需要在setup.py里配置entry_points。这个后面会讲到。
2.3 编写第一个C++节点
在my_cpp_pkg/src下新建一个文件,叫cpp_node.cpp。写一个最简单的发布者节点。
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
int main(int argc, char *argv[])
{
rclcpp::init(argc, argv);
auto node = std::make_shared<rclcpp::Node>("cpp_node");
auto publisher = node->create_publisher<std_msgs::msg::String>("topic", 10);
rclcpp::WallRate rate(1);
while (rclcpp::ok())
{
auto msg = std_msgs::msg::String();
msg.data = "Hello from C++ node!";
RCLCPP_INFO(node->get_logger(), "Publishing: '%s'", msg.data.c_str());
publisher->publish(msg);
rclcpp::spin_some(node);
rate.sleep();
}
rclcpp::shutdown();
return 0;
}
这段代码干了什么?
- 初始化ROS2
- 创建一个节点,名字叫
cpp_node - 创建一个发布者,往
topic话题上发字符串消息 - 每秒发一次,循环不断
然后,修改CMakeLists.txt,加上编译规则:
add_executable(cpp_node src/cpp_node.cpp)
ament_target_dependencies(cpp_node rclcpp std_msgs)
install(TARGETS
cpp_node
DESTINATION lib/${PROJECT_NAME}
)
install那一段,结果编译成功但运行时找不到可执行文件。折腾了半小时才发现……所以,install这一步千万别省。
2.4 编写第一个Python节点
在my_py_pkg/my_py_pkg目录下新建py_node.py:
import rclpy
from rclpy.node import Node
from std_msgs.msg import String
class PyNode(Node):
def __init__(self):
super().__init__('py_node')
self.publisher_ = self.create_publisher(String, 'topic', 10)
self.timer = self.create_timer(1.0, self.timer_callback)
def timer_callback(self):
msg = String()
msg.data = 'Hello from Python node!'
self.get_logger().info(f'Publishing: "{msg.data}"')
self.publisher_.publish(msg)
def main(args=None):
rclpy.init(args=args)
node = PyNode()
rclpy.spin(node)
node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
Python版本用了类的方式,更符合面向对象习惯。定时器每1秒触发一次回调,发布消息。
然后修改setup.py,添加入口点:
entry_points={
'console_scripts': [
'py_node = my_py_pkg.py_node:main',
],
},
这样,ros2 run my_py_pkg py_node就能找到你的节点了。
2.5 编译与运行
回到工作空间根目录,编译:
cd ~/ros2_ws
colcon build
如果一切顺利,你会看到绿色的Finished字样。然后source环境:
source install/setup.bash
运行C++节点:
ros2 run my_cpp_pkg cpp_node
运行Python节点:
ros2 run my_py_pkg py_node
你会看到终端里每秒打印一条消息。恭喜,你的第一个ROS2节点跑起来了!
package not found。所以,每次打开新终端,记得先source install/setup.bash。或者干脆把它写到~/.bashrc里,一劳永逸。
2.6 知识体系总览
下面这张图,帮你理清这一章的核心逻辑:
说白了,这一章就干了三件事:搭环境、建包、写节点。你想想看,是不是很简单?但就是这三步,构成了你后续所有ROS2开发的基石。
嗯,到这里,你已经有了自己的第一个C++和Python节点。接下来,你可以试着改改话题名称、消息内容,或者同时运行两个节点看看它们怎么通信。动手试试,比看十遍都管用。